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)利用 移位 + 位與 的方式對“行狀態”整體判斷算是常用套路了吧