首页 > 代码库 > 状态压缩动态规划总结
状态压缩动态规划总结
状态压缩是一个很广的概念,在OI中也有很多的应用,当我们把他应用到动态规划中,可以用来精简状态,节约空间,也方便转移。最常见的就是用二进制来表是状态,利用各种位移运算,就可以实现\(O(1)\)的转移。状压DP适用于“窄棋盘”上的DP,否则状态太多无法存下。
POJ1185 炮兵阵地
题意:给一个\(N \times M\)的地形盘,有平原和山坡,要求在平原上部署尽量多的炮(攻击范围2),使得不互相攻击。
数据范围:N <= 100;M <= 10,符合条件。如何表示状态?按行DP,一个二进制数(m<=10,int足够了,最大状态数:(1 << 10) = 1024,但有很多不合法状态)上的某位等于1表示在此处有炮。用F[i][j][k]表示在i行,当前行状态为j,前一列状态为k时的最优解。二进制的优势在此时体现出来了:我可以\(O(1)\)判断状态是否合法,详见代码,此处需要深入体会。
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 6 const int maxn = 60 + 10; 7 8 int G[maxn*2],State[maxn],Num[maxn*2],dp[maxn*2][maxn][maxn]; 9 10 int main()11 {12 int n,m;13 int Snum=0;14 15 scanf("%d%d\n",&n,&m);16 for(int i=0;i<n;++i)17 {18 for(int j=0;j<m;++j)19 {20 char c=getchar();21 if(c==‘H‘)22 G[i]+=1<<j;23 }24 getchar();25 }26 27 for(int i=0;i<(1<<m);++i)28 {29 if((i&(i<<1)) || (i&(i<<2)))30 continue;31 int k=i;32 while(k)33 {34 Num[Snum]+=k&1;35 k>>=1;36 }37 State[Snum++]=i;38 }39 40 for(int i=0;i<Snum;++i)41 {42 if(State[i]&G[0])43 continue;44 dp[0][i][0]=Num[i];45 }46 47 for(int i=0;i<Snum;++i)48 {49 if(State[i]&G[1])50 continue;51 for(int j=0;j<Snum;++j)52 {53 if((State[i]&State[j]) || (State[j]&G[0]))54 continue;55 dp[1][i][j]=max(dp[1][i][j],dp[0][j][0]+Num[i]);56 }57 }58 59 for(int i=2;i<n;++i)60 {61 for(int j=0;j<Snum;++j)62 {63 if(State[j]&G[i])64 continue;65 for(int k=0;k<Snum;++k)66 {67 if(State[k]&State[j])68 continue;69 if(State[k]&G[i-1])70 continue;71 for(int p=0;p<Snum;++p)72 {73 if(State[p]&G[i-2])74 continue;75 if(State[p]&State[k])76 continue;77 if(State[p]&State[j])78 continue;79 dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][p]+Num[j]);80 }81 }82 }83 }84 85 int ans=0;86 for(int i=0;i<Snum;++i)87 for(int j=0;j<Snum;++j)88 ans=max(ans,dp[n-1][i][j]);89 printf("%d\n",ans);90 return 0;91 }
POJ3254 Corn Fields
题意:一个row*col的矩阵,每个格子是0或者1,1表示土壤肥沃可以种植草地,0则不可以。在种草地的格子可以放牛,但边相邻的两个格子不允许同时放牛,问总共有多少种放牛的方法?(不放牛也算一种情况)
数据范围:1 ≤ M ≤ 12; 1 ≤ N ≤ 12,同样的小棋盘模型。做法其实都差不多,关键是状态的转移,这道题比上一道题要容易些。
1 #include <cstdio> 2 #include <cstring> 3 const int maxn = 15; 4 const int maxs = 5000; 5 const int Mod = 1e8; 6 7 int State[maxs],Snum; 8 int G[maxn]; 9 int dp[maxn][maxs];10 int n,m;11 12 void init()13 {14 for(int i=0;i<(1<<m);++i)15 if(!(i&(i<<1)))16 State[Snum++]=i;17 }18 19 int main()20 {21 freopen("data.out","w",stdout);22 freopen("data.in","r",stdin);23 24 scanf("%d%d",&n,&m);25 for(int i=0;i<n;++i)26 for(int j=0;j<m;++j)27 {28 int t;29 scanf("%d",&t);30 if(t==0)31 G[i]+=(1<<j);32 }33 init();34 35 for(int i=0;i<Snum;++i)36 if(!(State[i]&G[0]))37 dp[0][i]=1;38 39 for(int i=1;i<n;++i)40 for(int j=0;j<Snum;++j)41 {42 if((State[j]&G[i]))43 continue;44 for(int k=0;k<Snum;++k)45 {46 if((State[k]&G[i-1]) || (State[k]&State[j]))47 continue;48 dp[i][j]=(dp[i][j]+dp[i-1][k])%Mod;49 }50 }51 int ans=0;52 for(int i=0;i<Snum;++i)53 ans=(ans+dp[n-1][i])%Mod;54 printf("%d\n",ans);55 return 0;56 }
BZOJ1087: [SCOI2005]互不侵犯King
题意:在N×N的棋盘里面放K个国王,使他们互不攻击,求共有多少种摆放方案。
数据范围:1 <=N <=9, 0 <= K <= N * N,好小的N。。。国王的攻击范围是什么?想清楚了就是sb题了。
贴一个以前写的题解:
当前状态只会影响下一行的状态。用f[i][j][k]表示行i处状态为j时使用k个棋子可行的方案数。判状态转移时,两个状态S1与S2融洽的条件是!((S1<<1)&S2) && !((S1>>1)&S2) && !(S1&S2),且S1本身可行的条件是:!((S1<<1)&S1) && !((S1>>1)&s1)。
位运算真是个好东西。。
1 #include <cstdio> 2 #include <cstring> 3 4 const int maxn = 512 + 20; 5 6 typedef long long LL; 7 8 LL f[10][maxn][maxn]; 9 bool ok[10][maxn];10 int main()11 {12 13 int n,m;14 scanf("%d%d",&n,&m);15 16 memset(f,0,sizeof f);17 memset(ok,false,sizeof ok);18 19 f[0][0][0]=1;20 ok[0][0]=true;21 for(int i=1;i<=n;++i)22 {23 for(int j=0;j<(1<<n);++j)24 {25 if(ok[i-1][j])26 {27 for(int k=0;k<(1<<n);++k)28 {29 if(((k<<1)&k) || ((k>>1)&k)) continue;30 if((j&k) || ((j<<1)&k) || ((j>>1)&k)) continue;31 int tmp=k,cnt=0;32 while(tmp)33 {34 if(tmp&1)35 ++cnt;36 tmp>>=1;37 }38 ok[i][k]=true;39 for(int p=0;p+cnt<=m;++p)40 f[i][k][p+cnt]+=f[i-1][j][p];41 }42 }43 }44 }45 46 LL ans=0;47 for(int j=0;j<(1<<n);++j)48 ans+=f[n][j][m];49 printf("%lld\n",ans);50 return 0;51 }
看了3道棋盘规划型的题,我们来看一些棋盘覆盖的例子。
POJ2411 Mondriaan‘s Dream
题意:一个h*w的矩阵,希望用2*1或者1*2的矩形来填充满,求填充的总方案数。
数据范围:1<=h,w<=11。这道题是非常经典的棋盘覆盖状压DP。讲一个优化:先将可以兼容的状态dfs出来。详见代码。
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <vector> 5 using namespace std; 6 7 const int maxn = 14; 8 const int maxs = 40000; 9 typedef unsigned long long UI;10 11 int n,m;12 vector<int> State[maxs];13 int Snum;14 UI dp[maxn][maxs];15 16 void dfs(int col,int S1,int S2)17 {18 if(col>=m)19 {20 if(S1<(1<<m) && S2<(1<<m))21 State[S2].push_back(S1);22 return;23 }24 dfs(col+1,(S1<<1)|1,S2<<1);25 dfs(col+1,S1<<1,(S2<<1)|1);26 dfs(col+2,(S1<<2)|3,(S2<<2)|3);27 }28 29 void solve()30 {31 Snum=1<<m;32 memset(dp,0,sizeof dp);33 for(int i=0;i<Snum;++i)34 State[i].clear();35 dfs(0,0,0);36 dp[0][0]=1;37 for(int i=1;i<=n+1;++i)38 for(int j=0;j<Snum;++j)39 for(int k=0,sz=State[j].size();k<sz;++k)40 dp[i][j]+=dp[i-1][State[j][k]];41 printf("%lld\n",dp[n+1][Snum-1]);42 }43 44 int main()45 {46 while(scanf("%d%d",&n,&m)!=EOF && n!=0 && m!=0)47 {48 if((n&1) && (m&1))49 {50 puts("0");51 continue;52 }53 if(n<m)54 swap(n,m);55 solve();56 }57 return 0;58 }
SGU131 Hardwood floor
题意:在\(N \times M\)的棋盘内放1*2及2*2去掉一个角的块,问方案数。
数据范围:1<=M<=9, 1<=N<=9。原理是一样的,同样可以dfs出兼容的状态。但是具体如何实现还是有问题的,因为第二种块太奇葩了(第二种块:我tm哪里奇葩啦!)。dfs多传入两个参数:f1, f2,代表上一列摆放块对当前的影响。
1 #include <string> 2 #include <bitset> 3 #include <cstdio> 4 #include <cstring> 5 #include <algorithm> 6 #include <vector> 7 using namespace std; 8 9 const int maxn = 11;10 const int maxs = 1000;11 12 typedef long long LL;13 14 LL dp[maxn][maxs];15 vector<int> State[maxs];16 int n,m,Snum;17 18 bitset<13> tmp;19 void dfs(int col, int s1, int s2, int f1, int f2) {20 if(m <= col) {21 if(!f1 && !f2) {22 /*23 tmp = s1;24 printf("%s ", tmp.to_string().c_str());25 tmp = s2;26 printf("%s ", tmp.to_string().c_str());27 tmp = s2 ^ (Snum - 1);28 printf("%s\n", tmp.to_string().c_str());29 */30 State[s2 ^ (Snum - 1)].push_back(s1);31 }32 return ;33 }34 dfs(col + 1, (s1 << 1) + f1, (s2 << 1) + f2, 0, 0);35 // 可以尝试把上面那句话去掉36 // 。。。然后你就只能得到一种状态:放满了的。。37 if(!f1) {38 if(!f2) {39 dfs(col + 1, (s1 << 1) + 1, (s2 << 1) + 1, 0, 0);40 dfs(col + 1, (s1 << 1) + 1, (s2 << 1) + 1, 1, 0);41 dfs(col + 1, (s1 << 1) + 1, (s2 << 1) + 1, 0, 1);42 }43 dfs(col + 1, (s1 << 1) + 1, (s2 << 1) + f2, 1, 0);44 dfs(col + 1, (s1 << 1) + 1, (s2 << 1) + f2, 1, 1);45 }46 if(!f2) {47 dfs(col + 1, (s1 << 1) + f1, (s2 << 1) + 1, 1, 1);48 }49 }50 51 LL solve() {52 dp[0][Snum-1]=1;53 for(int i=1;i<=n;++i)54 for(int j=0;j<Snum;++j)55 for(int k=0,sz=State[j].size();k<sz;++k)56 dp[i][j]+=dp[i-1][State[j][k]];57 return dp[n][Snum-1];58 }59 60 int main() {61 scanf("%d%d",&n,&m);62 if(n<m)63 swap(n,m);64 Snum=1<<m;65 dfs(0, 0, 0, 0, 0);66 printf("%lld\n",solve());67 return 0;68 }
SGU132 Another Chocolate Manic
详见此处:http://www.cnblogs.com/hzf-sbit/p/3870652.html
OK,以上就是本人最近(呵呵)做的几道状态压缩DP题。
然后状态压缩还有个很好玩的东东:基于联通性的状态压缩动态规划,俗称:插头DP,以后再总结。。。
PS:欢迎指教~~