狀態壓縮動態規劃總結

狀態壓縮是一個很廣的概念,在OI中也有不少的應用,當咱們把他應用到動態規劃中,能夠用來精簡狀態,節約空間,也方便轉移。最多見的就是用二進制來表是狀態,利用各類位移運算,就能夠實現\(O(1)\)的轉移。狀壓DP適用於「窄棋盤」上的DP,不然狀態太多沒法存下。php

 POJ1185 炮兵陣地

題意:給一個\(N \times M\)的地形盤,有平原和山坡,要求在平原上部署儘可能多的炮(攻擊範圍2),使得不互相攻擊。html

數據範圍:N <= 100;M <= 10,符合條件。如何表示狀態?按行DP,一個二進制數(m<=10,int足夠了,最大狀態數:(1 << 10) = 1024,但有不少不合法狀態)上的某位等於1表示在此處有炮。用F[i][j][k]表示在i行,當前行狀態爲j,前一列狀態爲k時的最優解。二進制的優點在此時體現出來了:我能夠\(O(1)\)判斷狀態是否合法,詳見代碼,此處須要深刻體會。ide

 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 }
View Code

POJ3254 Corn Fields

題意:一個row*col的矩陣,每一個格子是0或者1,1表示土壤肥沃能夠種植草地,0則不能夠。在種草地的格子能夠放牛,但邊相鄰的兩個格子不容許同時放牛,問總共有多少种放牛的方法?(不放牛也算一種狀況)優化

數據範圍:1 ≤ M ≤ 12; 1 ≤ N ≤ 12,一樣的小棋盤模型。作法其實都差很少,關鍵是狀態的轉移,這道題比上一道題要容易些。spa

 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 }
View Code

BZOJ1087: [SCOI2005]互不侵犯King

題意:在N×N的棋盤裏面放K個國王,使他們互不攻擊,求共有多少種擺放方案。code

數據範圍:1 <=N <=9, 0 <= K <= N * N,好小的N。。。國王的攻擊範圍是什麼?想清楚了就是sb題了。
htm

貼一個之前寫的題解:blog

當前狀態只會影響下一行的狀態。用f[i][j][k]表示行i處狀態爲j時使用k個棋子可行的方案數。判狀態轉移時,兩個狀態S1與S2融洽的條件是!((S1<<1)&S2) && !((S1>>1)&S2) && !(S1&S2),且S1自己可行的條件是:!((S1<<1)&S1) && !((S1>>1)&s1)。部署

位運算真是個好東西。。get

 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 }
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\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 }
View Code

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 }
View Code

SGU132 Another Chocolate Manic

詳見此處:http://www.cnblogs.com/hzf-sbit/p/3870652.html

 

OK,以上就是本人最近(呵呵)作的幾道狀態壓縮DP題。

而後狀態壓縮還有個很好玩的東東:基於聯通性的狀態壓縮動態規劃,俗稱:插頭DP,之後再總結。。。

PS:歡迎指教~~

相關文章
相關標籤/搜索