dp深入与进阶(3):状压dp(2025.02.03)
\(01.\)状压dp概念:
通过将状态压缩为整数(二进制数),从而优化状态转移的方法即为状压dp
而实际使用和操作时可以考虑位运算,还可以在一定程度上加快程序运行
优点:
1.简化复杂状态:用二进制压缩表示复杂状态,如 TSP 问题里城市访问状态
2.降低空间开销:相比传统记录方式,大幅减少状态存储的空间。
3.提升时间效率:利用位运算快速操作状态,减少计算量。
4.应用广泛:适用于棋盘布局、资源分配等多领域问题。
5.实现简便:基于二进制运算与 DP 框架,代码易理解与调试。
(
以上内容出自豆包)
\(02.\)状压dp的实现:Luogu P1896 [互不侵犯]
\(i.\)状态压缩
我们以行为单位,对于每一行放/不放王的状态,压缩为二进制01串
0 \(\rightarrow\)该位置不放王
1\(\rightarrow\)该位置放王
例:
对于上图,有状态\(s=(101001000)_2=328\)
如此便可以用二进制数来存储所有的状态
\(ii.\)条件判断
\(Q:\)
对于每次个王,
满足上、下、左、右、左上、左下、右上、右下八个方向上相邻的格子都不能有相邻的王
如何进行这样的判断呢?
\(A:\)
我们假设第\(i\)行状态为\(s_1\),而前一行的状态为\(s_2\)
\(1.\)因为单行内左右不可为王,即要满足((s1<<1)&s1==0)&&((s1>>1)&s1==0) 简写!((s1<<1)|(s1>>1)&s1)
\(2.\)而相邻行不能为有距离大于$\sqrt 2 $的位置为王,即((s1<<1)&s2==0)&&(s1&s2==0)&&((s1>>1)&s2==0) 简写!((s1<<1)|s1|(s1>>1)&s2)
可以把判断满足条件的语句封装为函数\(sta(s1,s2)\),满足条件返回1,不满足返回0,便于调用
\(iii.\)状态转移
(1)状态设计:
考虑f[i][num][s]表示第i行,使用num个棋子,当前状态为s的可行方案数
其中满足\(s_1\)为本行(第i行)的状态,\(s_2\)为上一行的状态
(2)公式推导:
对于所有满足条件的\(s_1\),\(s_2\),所有\(s_2\)的方法数\(s_1\)都满足
即
其中\(get_-num(s_1)\)函数用来统计\(s_1\)中数字\(1\)的个数
初始化:\(f(0,0,0)=1\),第\(0\)行\(0\)个棋子状态为\(0\)的方案为\(1\)
\(iv.\)代码实现
#include<bits/stdc++.h>
using namespace std;
long long f[10][100][2010];
//判断s1,s2是否满足条件
bool sta(int s1,int s2)
{
if(((s1<<1)|(s1>>1))&s1)
return 0;
if(((s2<<1)|(s2>>1))&s2)
return 0;
if(((s1<<1)|s1|(s1>>1))&s2)
return 0;
return 1;
}
//计算s二进制中有多少个1
int get_num(int s)
{
int ret=0;
while(s)
{
ret+=(s&1);//取二进制最后一位的1加入ret
s>>=1;//s右移一位
}
return ret;
}
int main()
{
int n,k;
cin>>n>>k;
f[0][0][0]=1;
int maxn=1<<n;
for(int i=1;i<=n;++i)//枚举当前行i
for(int s1=0;s1<maxn;++s1)//枚举本行状态s1
for(int s2=0;s2<maxn;++s2)//枚举上一行状态s2
{
if(sta(s1,s2)==0)
continue;
for(int num=0;num<=k;++num)//枚举放了的棋子数
{
int num_s1=get_num(s1);
if(num-num_s1>=0)//判断是否可以进行dp
f[i][num][s1]+=f[i-1][num-num_s1][s2];
}
}
long long ans=0;
for(int i=0;i<maxn;++i)
ans+=f[n][k][i];
cout<<ans;
return 0;
}
//这个题有更优化的思路 需要进行预处理 感兴趣的可以看XichenOC的题解
//代码:SamXia
这个题有另一个题解XiChenOC-状压DP(学习笔记)
来自XiChenOC大佬,里面有本题的优化技巧,可以进行学习理解



浙公网安备 33010602011771号