[POJ 1185] 炮兵阵地
Link:
Solution:
看大家都说是一道状压$dp$基础题,结果我还是卡题了
发现决策第$i$行时,要同时考虑$i-1$和$i-2$行,因此状态中要包含两个“行”状态位
但$O(1024^3*100)$的复杂度明显不行啊,于是我就在这卡住了……
其实可以发现仅考虑“行合法”的行状态与前后行无关,可以预处理,而且只有不到70种!
于是将原来的$1024$种行状态缩减为已保证“行合法”的$70$中状态即可
这里对每行集体判断和 [BZOJ 2734] 集合取数 中 求表格上任意两点不相邻的点集数 的判断方式类似
用 !(st[l]&st[j]) 和 !(st[l]&st[k]) 判断与上两行的列是否有冲突
同时将原来输入的'N'、'P',转换为'1'、'0',判断位与是否不为0
Code:
#include <iostream> #include <cstdlib> #include <cstdio> #include <algorithm> #include <cstring> #include <vector> using namespace std; const int MAXN=110,MST=70; int n,m,dp[MAXN][MST][MST],dat[MAXN],sum[MST],st[MST],tot=0,res=0; int cal(int x) //计算其中1的个数 { int ret=0; while(x) ret+=(x&1),x>>=1; return ret; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) for(int j=0;j<m;j++) { char ch=getchar(); while(ch!='P' && ch!='H') ch=getchar(); if(ch=='H') dat[i]|=(1<<j); } for(int i=0;i<(1<<m);i++) //预处理出一行可能的状态 if(!(i&(i<<1)) && !(i&(i<<2))) st[++tot]=i,sum[tot]=cal(i); for(int i=1;i<=tot;i++) if(!(st[i]&dat[1])) dp[1][1][i]=sum[i]; for(int i=1;i<n;i++) for(int j=1;j<=tot;j++) for(int k=1;k<=tot;k++) if(!(st[j]&st[k]) && dp[i][j][k]) for(int l=1;l<=tot;l++) //通过位移和位与集体判断 if(!(st[l]&st[j]) && !(st[l]&st[k]) && !(st[l]&dat[i+1])) dp[i+1][k][l]=max(dp[i+1][k][l],dp[i][j][k]+sum[l]); for(int i=1;i<=tot;i++) for(int j=1;j<=tot;j++) res=max(res,dp[n][i][j]); printf("%d",res); return 0; }
Review:
(1)查看有没有可以事先预处理出的合法的状态来对总数缩减
(2)利用 移位 + 位与 的方式对“行状态”整体判断算是常用套路了吧