状态压缩dp入门

poj1321 http://poj.org/problem?id=1321

我们可以把棋盘的每一行看做是一个状态,如果某一列放置了棋子,那么就标记为1,否则就标记为0.然后把它看成是一个二进制数,然后转为10进制数,就可以当做数组下标然后进行状态转移了

设dp[i][s] 为处理到第i行时,状态为s的方法数

那么我们枚举第i-1行的所有状态s

dp[i][s] += dp[i-1][s]; //表示第i行不放置棋子的方法数

dp[i][s|(1<<j)] += dp[i-1][s] //表示第i行第j列放置棋子的方法数   (前提是 chess[i][j]=='#' && 状态s第j列没有放过棋子)

 

 1 #include <stdio.h>
 2 #include <string.h>
 3 char chess[9][9];int dp[11][1<<8];
 4 int n,k;
 5 int main()
 6 {
 7     int i,j,s,ss;
 8     while(scanf("%d%d",&n,&k)!=EOF)
 9     {
10         int ans = 0;
11         if(n==-1) return 0;
12         memset(dp,0,sizeof(dp));
13         for(i=1; i<=n; ++i)
14         {
15             scanf("%s",chess[i]);
16         }
17         dp[0][0] = 1;
18         for(i=1; i<=n; ++i)
19             for(s=0; s<(1<<n); ++s)
20             {
21                 
22                 for(j=0; j<n; ++j)
23                     if(chess[i][j]=='#'&&((1<<j)&s)==0)
24                         dp[i][s|1<<j] += dp[i-1][s];//表示第i行第j列放置棋子的方法数  
25                 dp[i][s] += dp[i-1][s]; //表示第i行不放置棋子的方法数
26             }
27         ///for(j=1; j<=n; ++j)
28         for(s=0; s<(1<<n); ++s)
29         {
30             i = s;
31             int cnt = 0 ;
32             for(;i;i-= i&-i)//统计状态s有多少个1,表示放了多少个棋子
33                 cnt ++;
34             if(cnt==k)
35                 ans+= dp[n][s];
36         }
37         printf("%d\n",ans);
38     }
39     return 0;
40 }

 poj3254 http://poj.org/problem?id=3254

本来这一题也想用上面那题的思路做,可是后来发现不行,因为上面那题一行只选择一个位置,而这题一行可以选择多个位置。

那么我们这题可以枚举多个位置,如果和上一行的状态不冲突,那么上一行的状态就能转移到这一行

 1 #include <stdio.h>
 2 #include <string.h>
 3 
 4 int field[13];
 5 int situation[13];
 6 int dp[13][1<<13];
 7 const int MOD = 100000000;
 8 bool isOk(int s)
 9 {
10     if(s&(s>>1)) return false;
11     return  true;
12 }
13 
14 int main()
15 {
16     int n,m,i,j,s,ss,cnt,ans;
17     while(scanf("%d%d",&n,&m)!=EOF)
18     {
19         ans = cnt = 0;
20         memset(dp,0,sizeof(dp));
21         memset(field,0,sizeof(field));
22         for(s=0; s<(1<<m); ++s) if(isOk(s)) situation[cnt++] = s;//记录所有可行的状态,即不能有相邻的1
23         for(i=0; i<n; ++i)
24             for(j=0; j<m; ++j)
25             {
26                 scanf("%d",&s);
27                 field[i] |= (!s)<<j;//这里这样存的含义是,如果situation[] & field[] 不为0,说明状态situation不行
28             }    
29         
30         //dp[i][j] 表示处理完前i行,第j个状态的方法数
31         for(i=0; i<cnt; ++i)
32             if( !(situation[i] & field[0]))
33                 dp[0][i] = 1;
34             
35         for(i=1; i<n; ++i)
36             for(s=0; s<cnt; ++s)
37             {
38                 if(situation[s]&field[i]) continue;//这一行的状态要合法
39                 for(ss=0; ss<cnt; ++ss)
40                 {
41                     if(situation[ss] & situation[s]) continue;//这一行的状态和上一行的状态不合法
42                     dp[i][s] += dp[i-1][ss];//只要上一行的状态ss和这一行的状态s不冲突,那么就能转化为状态s
43                     dp[i][s] %= MOD;
44                 }
45             }
46         for(i=0; i<cnt; ++i)
47                 ans = (ans + dp[n-1][i]) % MOD;
48         printf("%d\n",ans);
49 
50     }
51     return 0;
52 }

poj1185 http://poj.org/problem?id=1185

和上面那题差不多,只是当前行的状态不止和上一行有关,还和上上一行有关,所以数组要多开一维

奇怪的是为什么用scanf("%c");G++ac,C++wa    用scanf("%s")G++和C++都wa

 1 /*
 2 分析:一行的状态不能有 11(连续的两个1)   也能有101(隔一个空然后出现一个1)
 3 dp[i][j][k]  表示第i行状态为j第i-1行状态为k时,最多有多少个炮兵摆放
 4 dp[i][j][k] = max(dp[i][j][k],dp[i-1][k][t]+num[j]);
 5 */
 6 
 7 #include <stdio.h>
 8 #include <string.h>
 9 int situation[11];
10 char matrix[111][11];
11 int map[111];
12 int num[111];
13 int dp[111][100][100];
14 bool isOk(int s)
15 {
16     if(s&(s<<1)) return false;
17     if(s&(s<<2)) return false;
18     return true;
19 }
20 
21 int count(int s)
22 {
23     int ret = 0;
24     for(;s; s-= (s&-s))//统计状态s有多少个1
25         ret++;
26     return ret;
27 }
28 inline int max(const int &a, const int &b)
29 {
30     return a < b ? b : a;
31 }
32 int main()
33 {
34     int n,m,i,j,k,s,cnt,ans,t;
35     char ch;
36     while(scanf("%d%d",&n,&m)!=EOF)
37     {
38         cnt = ans = 0;
39         memset(dp,-1,sizeof(dp));
40         for(s=0; s<(1<<m); ++s) if(isOk(s)) 
41         {
42             num[cnt] = count(s);
43             situation[cnt++] = s;
44         }
45         
46         for(i=0; i<n; ++i)
47         {
48             
49             getchar();
50             map[i] = 0;
51             for(j=0; j<m; ++j)
52             {
53                 scanf("%c",&ch);
54                 if(ch=='H') map[i] |= 1<<j;
55             }
56         }
57         for(i=0; i<cnt; ++i)
58             if(!(situation[i] & map[0]))
59                 dp[0][i][0] = num[i];
60         for(i=1; i<n; ++i)
61             for(j=0; j<cnt; ++j)
62             {
63                 if(situation[j] & map[i]) continue;
64                 for(k=0; k<cnt; ++k)
65                 {
66                     if(situation[j] & situation[k]) continue;
67                     for(t=0 ;t<cnt; ++t)
68                     {
69                         if(situation[j] & situation[t]) continue;
70                         if(dp[i-1][k][t]==-1) continue;
71                         dp[i][j][k] = max(dp[i][j][k],dp[i-1][k][t] + num[j]);
72                     }
73                 }
74             }
75         for(i=0; i<n; ++i)
76             for(j=0; j<cnt; ++j)
77                 for(k=0; k<cnt; ++k)
78                     ans = max(ans,dp[i][j][k]);
79         printf("%d\n",ans);
80     }
81     
82     return 0;
83 }

 hdu3001 http://acm.hdu.edu.cn/showproblem.php?pid=3001

平常的状态压缩,都是某个位置放(1)或者不放(0),所以可以直接用二进制进行压缩
但是这题,每个点可以走两次,那么就可以标记为0,1,2 所以要用三进制进行压缩。
即用一个数组存储每个状态的三进制的每位

 1 #include <stdio.h>
 2 #include <string.h>
 3 const int INF = 1<<30;
 4 int three[11],situation[59049][11],edge[11][11],dp[59049][11];
 5 void init()
 6 {
 7     int i,t,cnt;
 8     three[0] = 1;
 9     for(i=1; i<=10; ++i)
10         three[i] = three[i-1] * 3;
11     for(i=0; i<59049; ++i)
12     {
13          t = i;
14          cnt = 0;
15          while(t)//存储状态的三进制的每一位
16          {
17              situation[i][cnt++] = t % 3;
18              t /= 3;
19          }
20     }
21 }
22 inline int min(const int &a, const int &b)
23 {
24     return a < b ? a : b;
25 }
26 int main()
27 {
28     init();
29     int n,m,i,j,a,b,c,ans,s;
30     while(scanf("%d%d",&n,&m)!=EOF)
31     {
32         
33         for(i=0; i<n; ++i)
34             for(j=0; j<n; ++j)
35                 edge[i][j] = INF;
36         for(i=0; i<n; ++i)
37             for(s=0; s<three[n]; ++s)
38                 dp[s][i] = INF;
39         ans = INF;
40         for(i=0; i<m; ++i)
41         {
42             scanf("%d%d%d",&a,&b,&c);
43             a--,b--;
44             if(edge[a][b] > c)
45                 edge[a][b] = edge[b][a] = c;
46         }
47         for(i=0; i<n; ++i) dp[three[i]][i] = 0;//处于源点的距离为0
48         //因为走过某些城市可以是任意组合的,所以枚举这些状态
49         for(s=0; s<three[n]; ++s)
50         {
51             bool flag = true;
52             for(i=0; i<n; ++i)
53             {
54                 if(situation[s][i]==0) flag = false;//状态s的第i位为0,说明没有走过所有的城市
55                 if(dp[s][i]==INF) continue;
56                 for(j=0; j<n; ++j)
57                 {
58                     if(i==j) continue;
59                     if(edge[i][j]==INF || situation[s][j]==2) continue;
60                     int nextS = s + three[j];
61                     dp[nextS][j] = min(dp[nextS][j],dp[s][i] + edge[i][j]);
62                 }
63             }        
64             if(flag)//走过所有的状态
65             {
66                 for(i=0; i<n; ++i)
67                     ans = min(ans,dp[s][i]);
68             }
69         }
70         if(ans == INF)
71             printf("-1\n");
72         else
73         printf("%d\n",ans);
74     }
75     return 0;
76 }

 

posted @ 2015-02-11 12:21  justPassBy  阅读(286)  评论(0编辑  收藏  举报