状态压缩动态规划总结

状态压缩是一个很广的概念,在OI中也有很多的应用,当我们把他应用到动态规划中,可以用来精简状态,节约空间,也方便转移。最常见的就是用二进制来表是状态,利用各种位移运算,就可以实现(O(1))的转移。状压DP适用于“窄棋盘”上的DP,否则状态太多无法存下。

 POJ1185 炮兵阵地

题意:给一个(N imes 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,&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
",ans);
90     return 0;
91 }
View Code

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
",ans);
55     return 0;
56 }
View Code

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
",ans);
50     return 0;
51 }
View Code

看了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
",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 }
View Code

SGU131 Hardwood floor

题意:在(N imes 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
", 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
",solve());
67     return 0;
68 }
View Code

SGU132 Another Chocolate Manic

详见此处:http://www.cnblogs.com/hzf-sbit/p/3870652.html

OK,以上就是本人最近(呵呵)做的几道状态压缩DP题。

然后状态压缩还有个很好玩的东东:基于联通性的状态压缩动态规划,俗称:插头DP,以后再总结。。。

PS:欢迎指教~~

原文地址:https://www.cnblogs.com/hzf-sbit/p/3870716.html