题目连接:http://acm.pku.edu.cn/JudgeOnline/problem?id=1185
一看题只感觉是状态的压缩,因为数据为100*10,但是不知道何从下手,如何状态压缩。后来看了周伟(天津大学)的论文上面该题的讲解分析,然后参考了网上这个牛的blog http://longzxr.blog.sohu.com/111314565.html。写了下代码。发现位运算比较好,非常位运算了解太少呀。。。算法:状态压缩。
给出一个估算公式:运用简单的组合数学知识可以求出:在格数为 m的一行上放棋子且相邻两个棋子中间的空格不能少于d的num=g[m+d+1],其中g[i]=1 (i=1..d+1); g[j]=g[j-d-1]+g[j-1] (j>d)(摘自《状态压缩》周伟)。
有上式可知一行全为平原时,最多有60种放法,那么开始可以枚举所有的放法。
对于第i行,一共的放法有60种,那么第i-2,i-1行的放法同样有60种,那么枚举这3行即可。剪掉与该行山地有冲突的放法,剪掉这三行里面有冲突的放法。
状态方程:dp[i][j][k]=max{dp[i-1][k][l]+c[j]};
代码:
#include<stdio.h> #include<string.h> const int MAXN=65; const int MAXM=105; int num[MAXN],c[MAXN],s[MAXM],ans;//泡到放法的记录,该种放法中有几颗炮弹,每一行对应能不能放炮弹 ,总共的方法 int dp[MAXM][MAXN][MAXN];//dp[i][j][k]代表第i行为第j种,第i-1行尾第k中放法的炮弹的最大值 int n,m; void meiju() { ans=0; for(int i=0;i<(1<<m);i++)//一共有 (1<<m)种放法 { int tmp=i; if(((tmp<<1)&i)||((tmp<<2)&i))continue;//判断该种方法是不是合符 两个以内的炮弹不互相攻击 num[ans]=i;//记录该种放法 c[ans]=(tmp&1);//记录该种放法有几颗炮弹 while(tmp=(tmp>>1)) c[ans]+=(tmp&1); ans++; } } int slove() { memset(dp,0,sizeof(dp)); for(int i=0;i<n;i++)//枚举第i行 { for(int j=0;j<ans;j++)//枚举第i行的ans中放法 { if(num[j]&s[i]) continue;//剪掉与该行有山地的冲突的放法 if(i==0) dp[i][j][0]=c[j];//初始化dp else if(i==1) { for(int k=0;k<ans;k++) // 枚举第2行 { if(num[k]&s[i-1]) continue; if(num[k]&num[j]) continue; else if(dp[i][j][k]<dp[i-1][k][0]+c[j]) dp[i][j][k]=dp[i-1][k][0]+c[j]; } } else { for(int k=0;k<ans;k++) // 枚举第2行 { if(num[k]&s[i-1])continue; if(num[k]&num[j]) continue; for(int l=0;l<ans;l++) //同上 枚举第3行 { if(num[l]&s[i-2])continue; if((num[l]&num[k])||(num[l]&num[j])) continue; if(dp[i][j][k]<dp[i-1][k][l]+c[j]) dp[i][j][k]=dp[i-1][k][l]+c[j]; } } } } } int Max=0; for(int i=0;i<ans;i++) for(int j=0;j<ans;j++) if(dp[n-1][i][j]>Max) Max=dp[n-1][i][j]; return Max; } int main() { char str[12]; while(scanf("%d%d",&n,&m)!=EOF) { memset(s,0,sizeof(s)); for(int i=0;i<n;i++) { scanf("%s",&str); for(int j=0;j<m;j++) if(str[j]=='H') s[i]+=(1<<j); //有山地则为1,否则为0 } meiju(); int ans=slove(); printf("%d/n",ans); } return 0; }