一類分割棋盤的 DP

1

HDU5180
給定一個 n*n 的棋盤,要在上面放 k 個車和任意多個王(車走直線,王走八連通),求有多少種方案使得車不能夠吃王,車不能吃車,王不能吃王。

觀察到放置一輛車以後這一行、這一列都不能放旗子。因此 K 輛車佔領 K 行 K 列。於是我們不直接枚舉車的位置,可以枚舉車佔領了哪些行列,然後王只能放在每個被劃分成的小矩形內。因此我們可以預處理每個大小的矩形內隨便放王的方案數,然後枚舉車佔領了哪些行,對列進行dp即可。

 #include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define fir first
#define sec second
#define ld long double
using namespace std;
const int mod=1000000007;
typedef pair <int,int> P;
P trans[2000010];
int sta[100000],cnt,p,d[21];
ll fac[21],h[21],g[21][21];
int read()
{
	int x=0;char c=getchar(),flag='+';
	while(!isdigit(c)) flag=c,c=getchar();
	while(isdigit(c)) x=x*10+c-'0',c=getchar();
	return flag=='-'?-x:x;
}
inline void add(int &a,int b) {a+b>=mod?a+=b-mod:a+=b;}
int f[17][1600],sum[21][21];
int main()
{
	for(int i=0;i<(1<<15);i++)
	{
		if((i&(i<<1))||(i&(i>>1))) continue;
		sta[++cnt]=i;
	}
	for(int i=1;i<=cnt;i++)
	{
		for(int j=1;j<=cnt;j++)
		{
			int ss=sta[i],tt=sta[j];
			if((ss&tt)||(ss&(tt<<1))||(ss&(tt>>1))) continue;
			trans[++p]=P(i,j);
		}
	}
	for(int i=1;i<=15;i++)
	{
		memset(f,0,sizeof(f));
		f[0][1]=1;
		for(int j=1;j<=15;j++)
		{
			for(int k=1;k<=p;k++)
			{
				int ss=trans[k].fir,tt=trans[k].sec;
				if((sta[ss]>=(1<<i))||(sta[tt]>=(1<<i))) continue;
				add(f[j][tt],f[j-1][ss]);
				add(sum[i][j],f[j-1][ss]);
			}
		}
	}
	fac[0]=1;
	for(int i=1;i<=15;i++) fac[i]=fac[i-1]*i%mod;
	int T=read();
	while(T--)
	{
		ll ans=0;
		int n=read(),K=read();
		for(int s=0;s<(1<<n);s++)
		{
			if(__builtin_popcount(s)!=K) continue;
			int lst=0,e=0;
			for(int i=1;i<=n;i++)
			{
				if(s&(1<<i-1)) 
				{
					if(i-lst>1) d[++e]=i-lst-1;
					lst=i;
				}
			}
			if(lst<n) d[++e]=n-lst;
			h[0]=1;
			for(int i=1;i<=n;i++) 
			{
				h[i]=1;
				for(int j=1;j<=e;j++) h[i]=h[i]*sum[d[j]][i]%mod;
			}
			memset(g,0,sizeof(g));
			g[0][0]=1;
			for(int i=1;i<=n;i++)
			{
				for(int j=1;j<=i;j++)
				{
					for(int k=0;k<i;k++)
					{
						g[i][j]=(g[i][j]+g[k][j-1]*h[i-k-1])%mod;
					}
				}
				ans=(ans+g[i][K]*h[n-i])%mod;
			}
			ans=(ans+g[0][K]*h[n])%mod;
		}
		cout<<ans*fac[K]%mod<<'\n';
	}
	return 0;
}
/*by DT_Kang*/

2

51nod 1518
在這裏插入圖片描述
n,m16n,m\leq 16

我們可以容斥。假設枚舉了某個行、列的集合是不穩定的,那麼也就把棋盤劃分成了若干個區域,每個區域是獨立的。我們同樣預處理每個區域的方案數。但是枚舉行、列的集合複雜度太高了。一個策略是枚舉行的集合,對列進行 DP。預處理我們採用輪廓線 DP,轉移複雜度 O(1)O(1),於是複雜度爲 O(n22n)O(n^22^n)

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define fir first
#define sec second
#define ld long double
using namespace std;
const int mod=1e9+7;
typedef pair <int,int> P;
int f[21][1<<18];
ll h[21],g[21],sum[21][21],d[21];
int read()
{
	int x=0;char c=getchar(),flag='+';
	while(!isdigit(c)) flag=c,c=getchar();
	while(isdigit(c)) x=x*10+c-'0',c=getchar();
	return flag=='-'?-x:x;
}
inline void add(int &a,int b) {a+b>=mod?a+=b-mod:a+=b;}
int main()
{
	for(int w=1;w<=16;w++)
	{
		int U=(1<<w)-1;
		memset(f[0],0,sizeof(f[0]));
		f[0][(1<<w)-1]=1;
		int now=0;
		for(int i=1;i<=16;i++)
		{
			for(int j=1;j<=w;j++,now^=1)
			{
				memset(f[now^1],0,sizeof(f[now]));
				for(int s=0;s<(1<<w);s++)	//輪廓線DP,枚舉當前格子向上放還是向左放
				{
					if(!f[now][s]) continue;
					if(!(s&(1<<w-1))) add(f[now^1][(s<<1|1)&U],f[now][s]);
					else 
					{
						if(!(s&1)&&j>1) add(f[now^1][(s<<1|3)&U],f[now][s]);
						add(f[now^1][(s<<1)&U],f[now][s]);
					}
				}
			}
			sum[i][w]=f[now][(1<<w)-1];
		}
	}
	int n,m;
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		ll ans=0;
		for(int s=0;s<(1<<m-1);s++)
		{
			int lst=0,e=0;
			for(int i=1;i<m;i++) if(s&(1<<i-1)) d[++e]=i-lst,lst=i;
			d[++e]=m-lst;
			for(int i=1;i<=n;i++)
			{
				h[i]=1;
				for(int j=1;j<=e;j++) h[i]=h[i]*sum[d[j]][i]%mod;
			}
			g[0]=1;ll tmp=0;
			for(int i=1;i<n;i++)
			{
				g[i]=0;
				for(int j=0;j<i;j++) g[i]=(g[i]-g[j]*h[i-j])%mod;
				tmp=(tmp+g[i]*h[n-i])%mod;
			}
			tmp=(tmp+g[0]*h[n])%mod;
			if(__builtin_popcount(s)&1) ans=(ans-tmp)%mod;
			else ans=(ans+tmp)%mod;
		}
		cout<<(ans%mod+mod)%mod<<'\n';
	}
	return 0;
}
/*by DT_Kang*/

小結

這兩道題有一個共同點:都有着“分界線”一樣的東西把棋盤劃分成若干部分,而且每個部分是獨立的,因此我們可以預處理每個部分的信息,然後對分界線進行處理。

還有就是,對於行和列的限制,如果我們要容斥,一種策略是枚舉行的集合,對列進行 DP。

發佈了89 篇原創文章 · 獲贊 26 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章