首页 > 其他分享 >状压 dp

状压 dp

时间:2024-08-19 11:39:24浏览次数:11  
标签:二进制 状压 枚举 骨牌 集合 now dp

定义

在动态规划中,可能存在以“集合”为状态的动态规划,应为集合不易表示,所以通常用一个二进制数来表示集合。具体的,二进制数的第 \(i\) 位即表示当前集合是否包含第 \(i\) 个元素。

技巧

因为许多位运算的运算优先级很迷,所以搞不明白就尽量用括号。

二进制操作

  • 将 \(n\) 位二进制数全部赋值为 \(1\):s=(1<<n)-1;

  • 位 \(i\) 赋值 \(1\):s=(s|(1<<(i-1)));

  • 位置 \(i\) 赋值 \(0\):s=(s&(~(1<<(i-1))));

  • 查询位置 \(i\) 为 \(1/0\):op=((s>>(i-1))&1;

  • 判断 \(T\) 是否属于 \(S\) 的子集:op=((s|t)==s);

  • 取出 \(S\) 后 \(k\) 位:t=(s&((1<<k)-1));

当然还有一些基础的,就不说了。

子集枚举

有两种方法,第一种是枚举集合,再判断是否属于子集。时间复杂度取决于二进制串的长度 \(m\),为 \(2^m\)。

实际上有一种更快捷的方式:

for(int s=now;;s=((s-1)&now)){
	if(!pre)break;//   不会枚举到空子集
	work...
	//if(!pre)break;   会枚举到空子集
}

时间会更快,具体快多少不知道。

元素枚举

如果要枚举集合中的元素,可以枚举每一位在判断是否为 \(1\),时间复杂度取决于串长,已经十分优秀。但为了应对 sb 卡常题目。可以使用 \(\text{lowbit}\) 运算优化这个过程。

for(int now=0;now<(1<<n);now++){
	for(int s=now,u=(s&-s);s;s^=u,u=(s&-s)){//log2(u) 为 now 中的一个 1 的位置
     	...                           
	}                                
}

对于 \(\log_2(u)\),通常可以预处理出一个数组,可以直接id[1<<i]=i+1。不过比较浪费空间,可以选取一个模数,预处理时 id[(1<<i)%mod]=i+1,访问时 id[u%mod]。模数 \(m\) 可以一个 \(100\sim 200\) 的质数,只要能使 \(2^{0\sim n}\bmod m\) 各不相同即可,可以多试试,例如 \(77,177\)。

好像存在完美算法,可以直接算模数,不过不会。

变量

为了避免混淆,一般用 now,pre,s,t 这样的变量名,而不是 i,j,k 这样的

例题

1.[USACO12MAR] Cows in a Skyscraper G

状压模板。

定义 \(dp_S\) 表示表示当前集合 \(S\) 中所有物品均分组时,最小的分组数量,枚举上一组分出物品的集合 \(T\),显然 \(T\) 为 \(S\) 子集。定义 \(w_S\) 表示集合 \(S\) 中所有物品总重,如果集合 \(T\) 中物品可以放到一组,则必须满足 \(w_T\le W\),综上可得:

\[dp_S=\max\limits_{T\subseteq S,w_T\le W}\{dp_T+1\} \]

边界:\(dp_{0}=0\)。

可以用到子集枚举的优化,就可以通过了。

双倍经验

2.最短 Hamilton 路径

令 \(dp_{S,u}\) 表示目前已经经过了 \(S\) 中的所有点,且目前位于点 \(u\) 上。显然,一个状态如果合法,必然满足 \(u\in S\)。

枚举上一步走到是哪一个点,记为点 \(v\),合法的 \(v\) 也必须满足 \(v\in S\)。如果上一步走到了 \(v\),那么经过的点集不包含 \(u\),即 \(S-\{u\}\)。定义 \(d_{u,v}\) 表示边 \((u,v)\) 的长。可得

\[dp_{S,u}=\min\limits_{v\in S}\{dp_{S-\{u\},v}+d_{u,v}\} \]

边界:\(dp_{\{1\},1}=0\),然后没了。

三倍经验

3.蒙德里安的梦想

注意到骨牌有两种摆放方式,分别是竖着放和横着放。如果横着放,那么状态只和这一行有关,如果竖着放,则和上一行有关,我们着重注意这一点设计状态。

我们记 \(dp_{i,S}\) 为当前第 \(i\) 行中,骨牌上半部分所在下标构成集合 \(S\) 时,铺满的方案数。我们考虑第 \(i\) 行和第 \(i-1\) 行的关系。

如果第 \(i,i-1\) 行的状态合法,必须满足第 \(i-1\) 行所记录的骨牌的上半部分与第 \(i\) 行的下半部分一一对应,即必须满足:

  • 第 \(i\) 行和 \(i-1\) 不能有同一个位置均为骨牌的上半部分。

  • 竖着放的骨牌外必须能够用横着放的骨牌填满。

对于第 \(1\) 点,我们将压缩的二进制串相与,如果存在某个位置为 \(1\),则必然不是合法的。对于第 \(2\) 点,将压缩的二进制串相或,则此时任何一个 \(1\) 的位置均被某个竖着放的骨牌覆盖,只需检查相邻 \(1\) 之间的 \(0\) 的个数是否为偶数个。

可以预处理出第二点中的合法集合的集合,记为 \(L\)。综上可得:

\[dp_{i,S}=\sum\limits_{S\cap T=\varnothing,S\cup T\in L}\{dp_{i-1,T}\} \]

不过本题的边界不好设置,可以特变 \(i=1\),更简单的做法是将边界设为 \(dp_{0,0}=1\)。

4.[SCOI2005] 互不侵犯

和上一题类似,甚至更简单一点。

注意掉 \(i,i-1\) 行的国王不能处在同一列,且中间至少相隔一列,预处理出相邻 \(1\) 之间不少于 \(1\) 个 \(0\) 的二进制串,记为 \(L\),然后就转换为上一题了,连转移方程都一模一样

统计答案就是 \(\sum\limits_{S\in L}dp_{n,S}\)。

更简单的一题

这题与上题的区别是,联通范围从八个方向变成了四个方向,且增加了“地形”限制,只需在输入时将“地形”压缩起来。在动态规划枚举状态时,只需判断是否属于对应行压缩串的子串即可。

当然,也可以子集枚举,具体看技巧。

5.[NOI2001] 炮兵阵地

其实还是和上面是同一道题。

不过略有不同,这道题中当前阶段不止依赖于上一阶段,还依赖于上上个阶段。这样的话我们就要同时记录两个阶段的状态。

设 \(dp_{i,S,T}\) 表示第 \(i\) 行放置的炮兵构成 \(S\),第 \(i-1\) 行构成 \(T\)。这样的话就可以从 \(dp_{i-1,T,G}\) 进行转移。无论是 \(S\) 或 \(T\),都必须满足题目中给出的“地形条件”。且不能有同一个位置都放置了炮兵。具体的,记 \(V_i\) 表示第 \(i\) 行可防止炮兵位置。合法的状态必须满足 \(S\subseteq V_i,T\subseteq V_{i-1},S\cap T=\varnothing\)。

其实这里和上一题一模一样。直接写出转移方程(默认状态是合法的)

\[dp_{i,S,T}=\max\limits_{S\cap G=\varnothing}\{dp_{i-1,T,G}+|G|\} \]

如果暴力存储状态的话,会空间爆炸。我们发现很多状态都是无用的,预处理出所有相邻 \(1\) 之间不少于 \(2\) 个 \(0\) 的二进制串,发现及时当 \(n=10\) 时,满足条件的二进制数不超过 \(75\) 个。我们记录这 \(75\) 个数就可以。

点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
const int N=105,M=(1<<10),inf=1e9+10;
int n,m,val[N],p,mk[N],dp[N][N][N],num[M],ans;
char ch;
inline bool check(int i,int s){
    return (mk[i]|s)==mk[i]?0:1;
}
int main(){
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>ch;
            if(ch=='P')mk[i]=(mk[i]<<1)+1;
            else mk[i]=(mk[i]<<1);
        }
    }
    for(int now=0;now<(1<<m);now++){
        bool f=1;int cnt=0,tmp=2;
        for(int i=0;i<m;i++){
            if((now>>i)&1){
                if(tmp<2){f=0;break;}
                else tmp=0;
            }
            else tmp++;
        }
        for(int s=now;s>0;s-=(s&-s))cnt++;
        if(f)val[++p]=now;
        num[now]=cnt;
    }
    dp[0][0][0]=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=p;j++){
            if(check(i,val[j]))continue;
            for(int k=1;k<=p;k++){
                dp[i][j][k]=-inf;
                if(val[j]&val[k])continue;
                if(check(i-1,val[k]))continue;
                for(int l=1;l<=p;l++){
                    if(check(i-2,val[l]))continue;
                    if((val[j]&val[l])||(val[k]&val[l]))continue;
                    dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][l]+num[val[j]]);
                }
                if(i==n)ans=max(ans,dp[i][j][k]);
            }
        }
    }
    cout<<ans<<endl;
    return 0;
}

6.yyy loves Maths VII

很恶心的一道题。

我们记当前扔掉的卡片构成的集合为 \(S\),扔掉 \(S\) 中所有卡片成功的方案数为 \(dp_S\),记集合 \(S\) 能走的路程为 \(w_{S}\),我们枚举上一步扔掉了那个卡片。

可以写出转移方程:

\[dp_{S}=\begin{cases} 0&w_S=b_1\text{ or }w_S=b_2\\ \sum\limits_{u\in S,T=S-\{u\}}dp_T&\text{other} \end{cases}\]

这样转移为 TLE。常数太大,可以利用 \(\text{lowbit}\) 优化转移的写法

然后就过了,不是很会做卡常题。

标签:二进制,状压,枚举,骨牌,集合,now,dp
From: https://www.cnblogs.com/zuoqingyuan/p/18367009

相关文章

  • @Async使用ThreadPoolTaskExecutor 多线程
    SpringBoot中的线程池ThreadPoolTaskExecutor,@Async的使用线程池@Configuration@EnableAsyncpublicclassExcutorConfig{@Bean(name="ThreadPoolTaskExecutor")publicThreadPoolTaskExecutorThreadPoolTaskExecutor(){ThreadPoolTaskExecutorex......
  • UDP的可靠性传输——KCP
    目录一:TCP是怎么做到可靠的?1、UDP和TCP的区别 2、ARQ协议(AutomaticRepeat-reQuest)2.1、ARQ协议-停等式(stop-and-wait)2.2、ARQ协议-回退n帧(go-back-n)2.3、ARQ协议-选择性重传(selectiverepeat)3、RTT和RTO4、流量控制5、如何进行流量控制(滑动窗口)6、流量控......
  • 【TCP/IP】UDP协议数据格式和报文格式
    学习一个网络协议,主要就是学习“数据格式”/“报文格式”源端口/目的端口端口号是属于传输层的概念UDP报头使用两个自己的长度来表示端口号之所以端口号的范围是0~65535,是因为底层网络协议做出了强制要求如果使用一个10w这样的端口,就会在系统底层被“截断”UDP......
  • ThreadPoolExecutor详解
    恰逢经济下行,鄙人工作、生活日趋艰难,原本美好的愿望,如今只能成为奢望。不知如何是好的我,只能精研近几年来因浮躁而荒废的知识。今天就想跟大家聊一个对我来讲看似熟悉实则陌生的工具——ThreadPoolExecutor。熟悉是因为在我负责的项目中它是一个出镜率最高演员;陌生是因为我对其......
  • 序列(dp+矩阵加速)
    第2题   序列 查看测评数据信息给定一个数集A,现在你需要构造一个长度为k的序列B,序列B的元素从数集A中任意挑选,要求B中任意相邻的两个数字的异或值二进制表示中1的个数是3的倍数,请问B的有多少种合法的构造方案?两种方案不同当且仅当存在B[i]在A中的位置不同。输入格式......
  • (nice!!!)LeetCode 552. 学生出勤记录 II(动态规划dp递归版、记忆化dfs)
    题目:552.学生出勤记录II思路:记忆化搜索dfs,细节看注释classSolution{public:constintmod=1e9+7;//状态f[u][a][b]表示:在选择第u个位置前,缺勤次数为a次,且当前连续迟到次数为b次时,可以得到的合法出勤次数intf[100010][5][5];intdfs(intu,int......
  • 幸运数(dp+矩阵加速)
    第1题   幸运数 查看测评数据信息小明认为只有数字4和7是幸运数字,其他数字都不是。如果一个整数的每个数字都是幸运数字,那么该整数就是幸运整数。给出一个数组numbers[1...n]。一个长度为L的幸运数列A[0],A[1],A[2],...A[L-1]必须同时满足:1、对于0<=i<L,    A[......
  • DP学习笔记
    动态规划算法与分治法类似,是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始......
  • 【动态规划、dp】[CSP-J 2022] 上升点列 题解
    题目描述在一个二维平面内,给定nnn个整数点(xi......
  • 24.8.18 DP训练
    A-Mondriaan'sDream求用\(1\times2\)的小矩形填满\(n\timesm\)的矩形的方案数sol数据范围超级小,考虑状压记录\(st_i\)表示\(i\)这个二进制状态下的连续\(0\)长度是否存在奇数设\(dp_{i,j}\)表示到第\(i\)列,且在\(j\)中\(1\)的位置和\(i+1\)列放了横......