第一次接觸狀壓DP
狀壓DP入門及理解
*(另類的暴力)*
一般狀態數不多的時候就會開數組,但是有的狀態並不好表示,於是,狀壓DP就產生了。
狀壓DP應該是分兩類的,一類是壓縮狀態,另一類是舍棄狀態。 我感覺初學狀壓DP難就難在二進制運算的應用,了解二進制運算符就顯得十分重要。
所以我們先看下表,如果有不會二進制簡單應用的請點擊https://blog.csdn.net/sinat_35121480/article/details/53510793(神犇請忽略...)
下面就可以看題了:
對於狀壓壓縮,入門題[USACO06NOV]玉米田和Corn Fields[SCOI2005]互不侵犯,思路基本上相同。
我自己做了這兩個題有兩點體會,如下:
第一點(第一題):我們可以通過循環預處理出所有的狀態,再在動態規劃的過程中判斷狀態是否可行,方案數累加即可。
這樣來,動態規劃的方程就很好推出來了(初學者可能有點困難)。
F[ I ][ j ] 表示前I行,第I行狀態為J的方案數,J存的是狀態,用二進制表示。
代碼如下:
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<bitset> using namespace std;//前 i 行 //當前行 狀態為j的方案數 int n,m,a[15][15],g[1<<12],f[15][1<<12],mod=1e8; int check(int x) { if(!((x<<1)&x)&&!((x>>1)&x)) return 1; else return 0; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&a[i][j]); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)//轉化成二進制 g[i]=(g[i]<<1)+a[i][j]; for(int i=0;i<(1<<m);i++) if(check(i)&&((i&g[1])==i))//後者表示在可以種植的地方選擇(全選/部分/不選)地方去種,有點子集的感覺。 f[1][i]=1; for(int i=2;i<=n;i++)//第i行 { for(int j=0;j<(1<<m);j++)//第i行狀態為J { if(check(j)&&((j&g[i])==j)) for(int k=0;k<(1<<m);k++)//第i-1行狀態為k { if(check(k)&&((k&g[i-1])==k)&&(!(k&j))) f[i][j]+=f[i-1][k],f[i][j]%=mod; } } } int ans=0; for(int i=0;i<(1<<m);i++) ans+=f[n][i],ans%=mod; printf("%d",ans%mod); return 0; }
[SCOI2005]互不侵犯
https://www.lydsy.com/JudgeOnline/problem.php?id=1087
第二點(第二題):我們可以通過DFS搜索出所有符合情況的方案記錄下來再進行動態規劃。
DFS過程中如果搜到行的盡頭,我們就保存狀態再返回,否則就進行下一步搜索。
搜索分兩種狀態:1.當前格子不放國王,搜索下一個格子 2.當前格子放國王,就得跳過下一個格子搜索。(代碼中有特別註釋)
我們先不要管在列方向上的約束,只管每一行的國王不能放在一起,每一列的國王不能放在一起那是下面要考慮的問題。
當所有滿足條件的行的狀態都搜索出來之後,就可以動態規了,中間剔除列上不符要求的狀態。
動態規劃方程也跟上題的差不多:
F[ I ][ J ] [ K ] 表示前 I 行 ,第 i 行狀態為J 總共選了K個國王的方案數。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #define ll long long int using namespace std; ll n,t,top,sum[2000],zt[2000],f[20][1000][300],ans;//位置 狀態 選了幾個 void dfs(ll z,ll s,ll ci) { if(ci>=n) { top++; zt[top]=z; sum[top]=s; return; } dfs(z,s,ci+1);//不選 dfs(z+(1<<ci),s+1,ci+2);//選 } int main(){ scanf("%lld%lld",&n,&t); dfs(0,0,0); for(ll i=1;i<=top;i++)f[1][i][sum[i]]=1; for(ll i=2;i<=n;i++) for(ll j=1;j<=top;j++) for(ll k=1;k<=top;k++) { if(zt[j]&zt[k])continue; if((zt[j]<<1)&zt[k])continue; if((zt[k]<<1)&zt[j])continue; for(ll q=sum[j];q<=t;q++){ f[i][j][q]+=f[i-1][k][q-sum[j]]; //前i行 第i行狀態為j 有q個國王的方案數。 } } for(ll i=1;i<=top;i++) ans+=f[n][i][t]; printf("%lld",ans); return 0; }
嗯,這道題是右上角的大佬教我的。
我對於狀壓DP的理解剛剛入門,可能還有很多說的不妥當的地方希望各位神犇評論告知。
2018-07-28
19:17:14
第一次接觸狀壓DP