棋盤式(基於連通性)DP模型總結
阿新 • • 發佈:2020-07-30
導語
狀壓DP分兩大類,一類是集合式,另一類就是棋盤式(即基於連通性)。
其中,集合式狀壓DP難度相較後者略大,形式複雜多變。而棋盤式狀壓DP的題目樣式都相差不多,解法也都殊途同歸,因此將這一類題進行總結,不難歸結出一套解題模板。
正文
我們先看以下三個例題。
國王
演算法概述
狀態表示:f[i,j,s]表示已經在前i行放了j個國王,且第i行的狀態為s的方案數。
狀態轉移:考慮第i-1行所有合法狀態x,則f[i,j,s]=∑f[i-1,j-count(s),x],其中count(s)表示s中國王的個數。
演算法流程:
1.考慮將一行的狀態進行壓縮,然後預處理所有合法狀態。
2.列舉所有合法狀態,對於每一種合法狀態,處理該狀態可由哪些狀態轉移過來。
3.迴圈列舉DP狀態,進行狀態轉移。
參考程式碼
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; const int N=12; int stt[1<<N]; int cnt[1<<N]; int pre[1<<N][1<<N]; int sum[1<<N]; ll f[N][N*N][1<<N]; int n,m,cnt_stt; bool check(int state) { for(int i=0;i<n-1;i++) if(state>>i&1 && state>>i+1&1)return false; return true; } int count(int state) { int ans=0; for(int i=0;i<n;i++) if(state>>i&1)ans++; return ans; } int main() { scanf("%d%d",&n,&m); for(int i=0;i<(1<<n);i++)if(check(i))stt[++cnt_stt]=i,cnt[i]=count(i); for(int i=1;i<=cnt_stt;i++) for(int j=1;j<=cnt_stt;j++) if((stt[i]&stt[j])==0 && check(stt[i]|stt[j]))pre[stt[i]][++sum[stt[i]]]=stt[j]; f[0][0][0]=1; for(int i=1;i<=n+1;i++) for(int j=0;j<=m;j++) for(int s=1;s<=cnt_stt;s++) { int a=stt[s]; if(j<cnt[a])continue; for(int x=1;x<=sum[a];x++) { int b=pre[a][x]; f[i][j][a]+=f[i-1][j-cnt[a]][b]; } } printf("%lld\n",f[n+1][m][0]); return 0; }
玉米田
演算法概述
狀態表示:f[i,a]表示已經種了前i行,且第i行的狀態為a的方案數。
狀態轉移:考慮第i-1行的所有合法狀態b,則f[i,a]=∑f[i-1,b]。
演算法流程:
1.預處理所有合法狀態。
2.預處理對於每一個合法狀態,能夠轉移到其的所有合法狀態的集合。
3.迴圈狀態表示,考慮當前行狀態是否與地圖限制相矛盾,然後迴圈狀態轉移。
參考程式碼
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N=14,M=1<<12,mod=1e8; int stt[1<<N]; int cnt[1<<N]; int pre[M][M]; int f[N][1<<N]; int g[N]; int n,m,cnt_stt; bool check(int state) { for(int i=0;i<m-1;i++) if(state>>i&1 && state>>i+1&1)return false; return true; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) for(int j=0;j<m;j++) { int x;scanf("%d",&x); g[i]+=!x<<j; } for(int i=0;i<(1<<m);i++)if(check(i))stt[++cnt_stt]=i; for(int i=1;i<=cnt_stt;i++) for(int j=1;j<=cnt_stt;j++) if((stt[i]&stt[j])==0)pre[stt[i]][++cnt[stt[i]]]=stt[j]; f[0][0]=1; for(int i=1;i<=n+1;i++) for(int j=1;j<=cnt_stt;j++) { int a=stt[j]; if(g[i]&a)continue; for(int s=1;s<=cnt[a];s++) { int b=pre[a][s]; (f[i][a]+=f[i-1][b])%=mod; } } printf("%d\n",f[n+1][0]); return 0; }
炮兵陣地
演算法概述
狀態表示:
狀態轉移:
演算法流程:
參考程式碼
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N=110,M=11; int stt[1<<M]; int cnt[1<<M]; int f[2][1<<M][1<<M]; int g[N]; int n,m,cnt_stt; bool check(int state) { for(int i=0;i<=m-2;i++) if((state>>i&1) && (state>>i+1&1 || state>>i+2&1))return false; return true; } int count(int state) { int ans=0; for(int i=0;i<m;i++)ans+=state>>i&1; return ans; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { static char ch[M]; scanf("%s",ch); for(int j=0;j<m;j++) if(ch[j]=='H')g[i]+=1<<j; } for(int i=0;i<(1<<m);i++) if(check(i))stt[++cnt_stt]=i,cnt[i]=count(i); for(int i=1;i<=n+2;i++) for(int j=1;j<=cnt_stt;j++) for(int k=1;k<=cnt_stt;k++) { int a=stt[j],b=stt[k]; if(g[i-1]&a|g[i]&b)continue; for(int x=1;x<=cnt_stt;x++) { int c=stt[x]; if((a&b)|(a&c)|(b&c))continue; f[i&1][a][b]=max(f[i&1][a][b],f[i-1&1][c][a]+cnt[b]); } } printf("%d\n",f[n+2&1][0][0]); return 0; }