状态压缩dp(hdu2167,poj2411)

hdu2167 http://acm.hdu.edu.cn/showproblem.php?pid=2167

给定一个N*N的板子,里面有N*N个数字,选中一些数字,使得和最大

要求任意两个选中的数字不相邻,相邻包括上下,左右和对角线相邻。

由于N<=15,用程序判断了一下,每一行的有效状态<1600个,如果记录这些状态,然后每一行枚举当前行的上一行的状态那么极端下有1600*1600*15的复杂度,TLE

所以卡在这里很久,想不到怎么优化。 然后看了别人的代码知道了用邻接表存储哪两个状态是相容的。这样就不用枚举那么多了。所以就过了。

 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <iostream>
 4 #include <sstream>
 5 using namespace std;
 6 int matrix[15][15];
 7 int dp[1<<15][15],situation[1600],bit[15],head[1000000],e;
 8 struct node
 9 {
10     int v,next;
11 }g[1000000];;
12 int max(const int &a, const int &b)
13 {
14     return a < b ? b : a;
15 }
16 
17 void addEdge(int u, int v)
18 {
19     g[e].v = v;
20     g[e].next = head[u];
21     head[u] = e++;
22 }
23 int count(int i, int ss, int n)//计算状体第i行的状态s所取的数的和
24 {
25     int ret = 0;
26     int j = 0;
27     for(j=0; j<n; ++j)
28     {
29         if(bit[j] & ss)
30             ret += matrix[i][j];
31     }
32     return ret;
33 }
34 bool check(int s, int ss,int n)//检查s和ss是否可容
35 {
36     
37     if(s&ss) return false;
38     if((s<<1)&ss) return false;
39     if((s>>1)&ss) return false;
40     return true;
41 }
42 int main()
43 {
44     int n,i,j,m,s,ss;
45     char str[100];
46     bit[i=0] = 1;
47     for(i=1; i<15; ++i)
48         bit[i] = bit[i-1]<<1;
49     m = 1<<15;
50     for(i=0,j=0; i<m; ++i)
51         if(!(i&(i<<1)))//求出有效的状态
52             situation[j++]= i;
53     while(gets(str))
54     {
55         memset(dp,0,sizeof(dp));
56         e = 0;
57         memset(head,-1,sizeof(head));
58         n = 0;
59         do
60         {
61             j = 0;
62             stringstream scin(str);
63             while(scin>>matrix[n][j]) j++;
64             n++;
65             gets(str);
66             if(str[0]=='\0') break;
67 
68         }while(true);
69         m = 1<<n;
70         for(i=0;situation[i]<m; ++i)//判断哪两个状体相容,然后用邻接表存储
71             for(j=0; situation[j]<m; ++j)
72                 if(check(situation[i],situation[j],n))
73                     addEdge(i,j);
74         for(i=0; situation[i]<m; ++i)
75             dp[situation[i]][0] = count(0,situation[i],n);
76         for(i=1; i<n; ++i)
77             for(s=0;situation[s]<m; ++s)
78             {
79                 for(j=head[s];j!=-1;j=g[j].next)
80                 {
81                     dp[situation[g[j].v]][i] = max(dp[situation[g[j].v]][i],dp[situation[s]][i-1]+count(i,situation[g[j].v],n));
82                 }
83             }
84         int ans = 0;
85         for(i=0; situation[i]<m; ++i)
86         {
87             ans = max(ans,dp[situation[i]][n-1]);
88         }
89         printf("%d\n",ans);
90     }
91     return 0;
92 }

 poj2411 http://poj.org/problem?id=2411

给定一个N*M的矩形,用1*2的矩阵填充满,问有多少种填充的方法。 N,M<=11

状态压缩dp,时间复杂度是N*(1<<M)*(1<<M),可以使用邻接表存储,哪些状态是相容的,这样子,就不用枚举所有的状态了。

状态是如何转移的呢?

首先第一行的状态必须是:如果有1,那么必须有连续的两个1.即不能有单独的一个1.

这样子是为了,第二行如果竖着放,那么就可以填充第一行的空缺。

怎么判断当前行的状态和上一行的状态不冲突的?详见bool check(int s, int ss)函数注释

 1 #include <stdio.h>
 2 #include <string.h>
 3 #define LL __int64
 4 int n,m;
 5 int e,head[1000000];
 6 LL dp[1<<11][11];
 7 struct node
 8 {
 9     int v,next;
10 }g[1000000];
11 
12 void addEdge(int a, int b)
13 {
14     g[e].v = b;
15     g[e].next = head[a];
16     head[a] = e++;
17 }
18 void swap(int &a, int &b)
19 {
20     int t = a;
21     a = b;
22     b = t;
23 }
24 bool check(int s)
25 {
26     while(s)
27     {
28         if( (s&1) && ((s>>1)&1))
29             s>>=2;
30         else if( (s&1) && !((s>>1)&1))
31             return false;
32         else if(!(s&1))
33             s>>=1;
34         
35     }
36     return true;
37 }
38 bool check(int s, int ss)
39 {
40     for(int i=0; i<m; ++i)
41     {
42         if(!(s&1) && !(ss&1)) return false;//上一行该位置为0,那么这一行该位置应该竖着放,否则不相容
43         else if(!(s&1)&&(ss&1)) 
44         {
45             s>>=1;ss>>=1;continue;//上一行为0,这一行竖着放
46         }
47         else if((s&1)&&!(ss&1)) 
48         {
49             s>>=1;ss>>=1;continue;//上一行该位置为1,这一行可以不放
50         }
51         else if((s&1)&&(ss&1))//上一行的该位置和这一行的该位置为1,那么这行的矩形是横着放
52         {
53             s>>=1,ss>>=1;i++;
54             if((s&1)&&(ss&1)) //即下一个位置,上一行和这一行都必须为1
55             {
56                 s>>=1;ss>>=1;continue;
57             }
58             else return false;
59         }
60     }
61     return true;
62 }
63 int main()
64 {    
65     int s,ss,t,i,j;
66     LL ans;
67     while(scanf("%d%d",&n,&m),n)
68     {
69         memset(dp,0,sizeof(dp));
70         if(n<m) swap(n,m);
71         ans = 0;
72         t = 1<<m;
73         memset(head,-1,sizeof(head));
74         e = 0;
75         for(s=0; s<t; ++s)
76             for(ss=0; ss<t; ++ss)
77                 if(check(s,ss))
78                     addEdge(s,ss);
79         if((n*m)%2==0)
80         {
81             for(s=0; s<t; ++s)
82                 if(check(s))
83                     dp[s][0] = 1;
84             for(i=1; i<n; ++i)
85                 for(s=0; s<t; ++s)
86                     for(j=head[s];j!=-1 && g[j].v<t;j=g[j].next)
87                         dp[g[j].v][i] += dp[s][i-1];
88                         
89             ans = dp[(1<<m)-1][n-1];
90         }
91         printf("%I64d\n",ans);
92     }
93     return 0;
94 }

 

posted @ 2015-03-04 20:59  justPassBy  阅读(200)  评论(0编辑  收藏  举报