1. 程式人生 > >BZOJ5205 [CodePlus 2018 3 月賽]白金元首與莫斯科

BZOJ5205 [CodePlus 2018 3 月賽]白金元首與莫斯科

傳送門

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;
}