BZOJ5205 [CodePlus 2018 3 月賽]白金元首與莫斯科
阿新 • • 發佈:2018-12-10
傳送門
emm在雅禮集訓的時候聽到的一道題 上來就覺得是插頭dp 最後果然是輪廓線狀壓233
我們簡化一下題意。 有一個n*m的網格,每個格子是空地或障礙物,詢問把每一個空地看成障礙物的情況下,用1*2的骨牌覆蓋(可以留有空地)的方案數 對1e9+7取模 bzoj和洛咕題面都掛了233
我們發現留有空地就很煩,所以我們可以把空地看成1*1的骨牌,這樣的話我們統計的方案數就是用1*1的骨牌和1*2的骨牌完全覆蓋網格的方案數。
骨牌覆蓋! ——》輪廓線狀壓!
但是我們發現如果對於每個格子直接計算的話 時間複雜度是O(n^3*2^m) 根本無法承受
所以我們考慮另一種做法 我們可以選擇對前後綴進行合併這樣的話複雜度就降到了O(n^2*2^m)
我們考慮如何對前後綴進行合併 即什麼樣的兩條輪廓線是合法的
對應紅色的格子作為我們的合併的格子的話 首先要求它上下兩個格子已經被覆蓋過了 然後就是上下對應的藍綠格子應該狀態相同 這樣才能豎著填滿棋盤(我們現在只考慮豎著因為橫向的覆蓋是在輪廓線dp的時候已經討論過了)
所以我們對前後分別進行一次輪廓線dp(討論橫著放1*2豎著放1*2放1*1和不放) 然後最後統計答案的時候進行合併即可
附程式碼。(哦對bzoj卡空間只能開到1<<17不過也夠了233)
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define inf 20021225 #define ll long long #define mdn 1000000007 using namespace std; int f[18][18][1<<17],g[18][18][1<<17]; int bit[18],n,m,top; int mp[18][18]; void add(int &x,int y){x=(x+y)%mdn;} void work() { //int top=(1<<m)-1; f[1][1][top]=1; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { int x=i,y=j+1; if(j==m) x=i+1,y=1; if(x>n) continue; for(int st=0;st<=top;st++) if(f[i][j][st]) { int tmp=f[i][j][st]; if((st&bit[j])==0&&mp[i][j]) continue; if((st&bit[j])==0) { add(f[x][y][st|bit[j]],tmp); continue; } if(mp[i][j]) add(f[x][y][st],tmp); else { add(f[x][y][st],tmp); add(f[x][y][st^bit[j]],tmp); if(j>1&&(st&bit[j-1])==0) add(f[x][y][st|bit[j-1]],tmp); } } } g[n][m][top]=1; for(int i=n;i;i--) for(int j=m;j;j--) { int x=i,y=j-1; if(j==1) x=i-1,y=m; if(x<1) continue; for(int st=0;st<=top;st++) if(g[i][j][st]) { int tmp=g[i][j][st]; if((st&bit[j])==0&&mp[i][j]) continue; //printf("%d %d %d %d\n",i,j,st,g[i][j][st]); if((st&bit[j])==0) { add(g[x][y][st|bit[j]],tmp); continue; } if(mp[i][j]) add(g[x][y][st],tmp); else { add(g[x][y][st],tmp); add(g[x][y][st^bit[j]],tmp); if(j<m&&(st&bit[j+1])==0) add(g[x][y][st|bit[j+1]],tmp); } } } } int main() { scanf("%d%d",&n,&m); bit[1]=1;top=(1<<m)-1; for(int i=2;i<=m;i++) bit[i]=bit[i-1]<<1; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&mp[i][j]); work(); for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { if(mp[i][j]) { printf("0 "); continue; } int ans=0; for(int s=0;s<=top;s++) { if(s&bit[j]) add(ans,(ll)f[i][j][s]*g[i][j][s]%mdn); //printf("%d %d %d %d\n",i,j,f[i][j][s],g[i][j][s]); } printf("%d ",ans); } printf("\n"); } return 0; }