最近被状态压缩DP虐得不行,今天终于决定正视自己的弱项,好好把DP练习一下,把今天做的几道状态压缩DP总结一下,一定要想办法摆脱DP弱菜这个标签!!!
http://poj.org/problem?id=3254
poj 3254 :
应该是最基础的状态压缩DP了吧,设dp[i][flag]表示第i行状态为flag时的排放总数,预处理一下dp[1][flag],对于dp[i][flag](i>=2),则dp[i][flag]=dp[i][flag]+dp[i-1][pre]当且仅当pre满足以下几个条件:
1:flag和pre都不含有相邻的1(二进制)
2:flag和pre分别满足第i行和第i-1行的约束条件。
3:flag和pre在同一位上不能同时为1(二进制)。
还是挺简单的,用位运算可以简单实现。代码如下:
#include <iostream> #include <string.h> #include <stdio.h> #include <vector> #define mod 100000000 using namespace std; vector<int> t; int check(int x) { int i; for(i=0;i<=10;i++) { int tmp=(1<<i)+(1<<(i+1)); if((x&tmp)==tmp) return 0; } return 1; } void init() { int i; t.push_back(0); for(i=1;i<(1<<12);i++) { if(check(i)) t.push_back(i); } } int dp[13][400]; int num[13]; int main() { //freopen("dd.txt","r",stdin); init(); int n,m,i,j; scanf("%d%d",&n,&m); memset(dp,0,sizeof(dp)); for(i=1;i<=n;i++) { int tmp=0; for(j=1;j<=m;j++) { int x; scanf("%d",&x); tmp=tmp*2+x; } num[i]=tmp; } int limit=1<<m,len=t.size(); long long ans=0; for(i=0;i<len;i++) { if(t[i]>=limit) break; int now=t[i]; if((now|num[1])==num[1]) { dp[1][i]=1; } } for(i=2;i<=n;i++) { for(j=0;j<len;j++) { if(t[j]>=limit) break; int now=t[j],s; if((now|num[i])==num[i]) { for(s=0;s<len;s++) { if(t[s]>=limit) break; int pre=t[s]; if((num[i-1]|pre)==num[i-1]&&(pre&now)==0) { dp[i][j]=(dp[i][j]+dp[i-1][s])%mod; } } } } } for(i=0;i<len;i++) { if(t[i]>=limit) break; ans=(ans+dp[n][i])%mod; } printf("%I64d\n",ans); return 0; }
hdu 4539:
腾讯编程马拉松复赛的题,话说就是这道题激起了我苦练DP的决心的。
经典的状态压缩DP,我们设dp[i][pre][now]表示第i行为now状态,第i-1行为pre状态时可以安排的最大士兵数量,这里的状态指的是每一行的士兵安排情况,我们把状态用二进制表示出来后,第i位为1表示在第i列放置一个士兵,为0表示不放,因为m<=10所以我们最多只要1024个状态就可以表示一行的每一个状态,事实上这1024个状态中有大部分是不合法的,(也就是有两个1其距离为2),所以我们可以预处理出所有的合法状态,(我们下面所讨论的状态都是合法的),对于dp[i][pre][now]我要怎么计算呢?
首先当然是这两个状态不能和所给的矩阵有冲突(也就是在不能人的地方放置了人),然后相邻两行不能有距离为2的1存在。满足上面的条件后,则dp[i][pre][now]=max(dp[i-1][ppre][pre]),其中ppre也要状态也要满足以上条件。我们最后求合法状态中的最大值即可,以上快速判断是否满足要求可以用位运算来判断,具体如何用请自己思考,或者参考我的代码。
#include <iostream> #include <string.h> #include <algorithm> #include <stdio.h> #include <vector> using namespace std; int dp[200][200][200]; vector<int> a[11]; int check(int x,int len) { int i; for(i=0;i<len-2;i++) { int tmp=(1<<i)+(1<<(i+2)); if((tmp&x)==tmp) { return 0; } } return 1; } int cot[1100]; int getnum(int x) { int sum=0; while(x) { if(x%2) sum++; x/=2; } return sum; } void init() { int i,j,tmp; for(i=0;i<=1024;i++) cot[i]=getnum(i); for(i=1;i<=10;i++) { tmp=(1<<i)-1; a[i].push_back(0); for(j=1;j<=tmp;j++) { if(check(j,i)) { a[i].push_back(j); } } } } int max(int a,int b) { return a>b?a:b; } int num[110]; int main() { init(); int n,m,x; while(scanf("%d%d",&n,&m)!=EOF) { memset(dp,0,sizeof(dp)); int i,j,tmp; for(i=1;i<=n;i++) { tmp=0; for(j=1;j<=m;j++) { scanf("%d",&x); tmp=tmp*2+x; } num[i]=tmp; } tmp=a[m].size(); int ans=0; for(i=0;i<tmp;i++) { if((num[1]|a[m][i])==num[1])//判断是否满足棋盘 { int now=a[m][i]; dp[1][0][i]=cot[now]; ans=max(ans,dp[1][0][i]); } } for(i=2;i<=n;i++) { for(j=0;j<tmp;j++) { if((num[i]|a[m][j])==num[i])//状态满足第i行 { int now=a[m][j]; int s,t; for(s=0;s<tmp;s++) { int pre=a[m][s]; if((num[i-1]|pre)==num[i-1]) { if(((now<<1)&pre)==0&&((now>>1)&pre)==0) { if(i==2) dp[i][s][j]=dp[1][0][s]+cot[now]; else { for(t=0;t<tmp;t++) { int ppre=a[m][t]; if((num[i-2]|ppre)==num[i-2]) { if((ppre&now)==0) { if(((ppre<<1)&pre)==0&&((ppre>>1)&pre)==0) dp[i][s][j]=max(dp[i][s][j],dp[i-1][t][s]+cot[now]); } } } } ans=max(ans,dp[i][s][j]); } } } } } } printf("%d\n",ans); } return 0; }
http://poj.org/problem?id=1185
和上一道题差不多(连名字都一样),只是判断合法状态的方法不同而已,大部分都是一样的。只是变了一点形式而已,这里不多说了。直接看代码。
#include <iostream> #include <string.h> #include <algorithm> #include <stdio.h> #include <vector> using namespace std; int dp[110][65][65]; vector<int> a[11]; int check(int x,int len) { int i; for(i=0;i<len-1;i++) { int tmp=(1<<i)+(1<<(i+2)); if((tmp&x)==tmp) { return 0; } tmp=(1<<i)+(1<<(i+1)); if((tmp&x)==tmp) return 0; } return 1; } int cot[1100]; int getnum(int x) { int sum=0; while(x) { if(x%2) sum++; x/=2; } return sum; } void init() { int i,j,tmp; for(i=0;i<=1024;i++) cot[i]=getnum(i); for(i=1;i<=10;i++) { tmp=(1<<i)-1; a[i].push_back(0); for(j=1;j<=tmp;j++) { if(check(j,i)) { a[i].push_back(j); } } } } int max(int a,int b) { return a>b?a:b; } int num[110]; char bo[110][12]; int main() { //freopen("dd.txt","r",stdin); init(); int n,m,x; scanf("%d%d",&n,&m); memset(dp,0,sizeof(dp)); int i,j,tmp; for(i=1;i<=n;i++) { scanf("%s",bo[i]+1); } for(i=1;i<=n;i++) { tmp=0; for(j=1;j<=m;j++) { if(bo[i][j]=='P') tmp=tmp*2+1; else tmp*=2; } num[i]=tmp; } tmp=a[m].size(); int ans=0; for(i=0;i<tmp;i++) { if((num[1]|a[m][i])==num[1]) { int now=a[m][i]; dp[1][0][i]=cot[now]; ans=max(ans,dp[1][0][i]); } } for(i=2;i<=n;i++) { for(j=0;j<tmp;j++) { if((num[i]|a[m][j])==num[i]) { int now=a[m][j]; int s,t; for(s=0;s<tmp;s++) { int pre=a[m][s]; if((num[i-1]|pre)==num[i-1]) { if((pre&now)==0) { if(i==2) dp[i][s][j]=dp[1][0][s]+cot[now]; else { for(t=0;t<tmp;t++) { int ppre=a[m][t]; if((num[i-2]|ppre)==num[i-2]) { if((ppre&now)==0) { dp[i][s][j]=max(dp[i][s][j],dp[i-1][t][s]+cot[now]); } } } } ans=max(ans,dp[i][s][j]); } } } } } } printf("%d\n",ans); return 0; }
一年前就看过了,当时根本就不敢碰,今天终于鼓起勇气发现并不是很难,没用long longWA一次,然后2Y。
我们设dp[i][flag]表示第i行为状态flag的排列总数,这里我们设竖着放为1(上面那一段在第i行),其他为0(为0不一定为横着放,以为有可能上一行是竖着放的)。我们先预处理第一行的情况,然后对于dp[i][flag],(2<=i<n),dp[i][flag]=dp[i-1][pre]当且仅当以下条件满足:
1:flag和pre在同一位上不能同时为1.即(pre&flag==0)
2 : 我们设合法状态的定义如下:若一个状态中相邻两个1之间0的个数均为为偶数,则称它为合法状态。如长度为4的状态中1001(9) 1100是合法状态,而1010 1101不是。(注意对于同一个状态,有些长度下是合法的,有些长度下是不合法的,如1001和01001,在长度4下为合法状态,在长度5下则不是)
则pre^now必须为合法状态(这里的^为异或运算)。
注意到我们不用求dp[n][flag],因为若第n-1行确定了,则最后一行也已近确定了,则我们只要计算dp[n-1][flag]中属于合法状态的flag,求它们的和即可。若h*w为奇数,之间输出0即可,否则若h为1,输出1,不然的话就按上面的方法求。
代码如下:其实可以加很多优化的,但人懒就没加了。。。
#include <iostream> #include <string.h> #include <stdio.h> #include <algorithm> #include <vector> using namespace std; vector<int> t[12]; int check(int x,int len) { int l=1,i; for(i=1;i<=len;i++) { if(x&(1<<(i-1))) { if((i-l)%2) return 0; l=i+1; } } if((len-l+1)%2) return 0; return 1; } void init() { int i,j; for(i=1;i<=11;i++) { int tmp=(1<<i)-1; t[i].push_back(0); for(j=1;j<=tmp;j++) { if(check(j,i)) t[i].push_back(j); } } } long long dp[12][1<<11]; long long solve(int h,int w) { if((h*w)%2) return 0; if(h==1) return 1; memset(dp,0,sizeof(dp)); int len=t[w].size(),i,j,k; for(i=0;i<len;i++) { int now=t[w][i]; if(i!=0||w%2==0) dp[1][now]=1; } int tt=(1<<w); for(i=2;i<=h-1;i++) { for(j=0;j<tt;j++) { int now=j; for(k=0;k<tt;k++) { int pre=k; if((pre&now)==0&&check(pre^now,w)) { if(pre!=0||now!=0||w%2==0) dp[i][now]+=dp[i-1][pre]; } } } } long long ans=0; for(i=0;i<len;i++) ans+=dp[h-1][t[w][i]]; return ans; } int main() { //freopen("dd.txt","r",stdin); init(); //printf("%d\n",t[11].size()); int h,w; memset(dp,0,sizeof(dp)); while(scanf("%d%d",&h,&w)) { if(h+w==0) break; printf("%I64d\n",solve(h,w)); } return 0; }