1. 程式人生 > 其它 >P1879 [USACO06NOV]Corn Fields G

P1879 [USACO06NOV]Corn Fields G

技術標籤:DP

題目:

給定一個 n × m n \times m n×m的網格狀土地 a a a,其中 a i , j a_{i,j} ai,j為1表示這塊草地可以種草,為0表示不能種草,且相鄰(有公共邊)的土地不能同時種草,問有多少種合法的種草方案。
( 1 ≤ n , m ≤ 12 ) (1 \le n,m \le 12) (1n,m12)

題解:

這是一道狀壓 d p dp dp的簡單題,但可以用多種狀壓 d p dp dp的技巧去解這道題。

  • 解法1
    d p i , s dp_{i,s} dpi,s表示前 i i i行且第 i i i行的種草狀態為 s s
    s
    的合法方案數,首先 s s s要滿足 s ∣ a i = a i s|a_i = a_i sai=ai(滿足土地的限制), ( s > > 1 ) & s = 0 (s>>1)\&s=0 (s>>1)&s=0(滿足相鄰土地不同為1的限制),可以從 d p i − 1 , s ′ dp_{i-1,s'} dpi1,s轉移過來,其中要滿足 s ′ & s = 0 s' \& s=0 s&s=0,邊界為 d p 0 , 0 = 1 dp_{0,0}=1 dp0,0=1
    複雜度: O ( n 2 m F m ) < O ( n 3. 2 m ) O(n2^mF_m)<O(n3.2^m)
    O(n2mFm)<O(n3.2m)
    ,其中 F m F_m Fm為斐波那契數列的第 m m m項,看似為 O ( n 4 m ) O(n4^m) O(n4m)的複雜度,其實在列舉第二維狀態時要滿足土地限制,那麼不會有連續兩個1這種狀態,我們令 f i , 0 / 1 f_{i,0/1} fi,0/1表示前 i i i位且第 i i i位為0/1的合法狀態數,轉移為 f i , 0 = f i − 1 , 0 + f i − 1 , 1 , f i , 1 = f i − 1 , 0 f_{i,0}=f_{i-1,0}+f_{i-1,1},f_{i,1}=f_{i-1,0} fi,0=fi1,0+fi1,1
    ,fi,1=
    fi1,0
    ,邊界為 f 1 , 0 = f 1 , 1 = 1 f_{1,0}=f_{1,1}=1 f1,0=f1,1=1,令 f 1 , 1 = F 1 , f 1 , 0 = F 2 f_{1,1}=F_1,f_{1,0}=F_2 f1,1=F1,f1,0=F2,則 f 2 , 1 = F 3 , f 2 , 0 = F 2 f_{2,1}=F_3,f_{2,0}=F_2 f2,1=F3,f2,0=F2,以此類推, f m , 0 = F m + 1 , f m , 1 = F m f_{m,0}=F_{m+1},f_{m,1}=F_{m} fm,0=Fm+1,fm,1=Fm,所以總的狀態數為 f m , 0 + f m , 1 = F m + 2 f_{m,0}+f_{m,1}=F_{m+2} fm,0+fm,1=Fm+2
    程式碼:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<string>
#include<bitset>
#include<sstream>
#include<ctime>
//#include<chrono>
//#include<random>
//#include<unordered_map>
using namespace std;

#define ll long long
#define ls o<<1
#define rs o<<1|1
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define sz(x) (int)(x).size()
#define all(x) (x).begin(),(x).end()
const double pi=acos(-1.0);
const double eps=1e-6;
const int mod=1e9;
const int INF=0x3f3f3f3f;
const int maxn=13;
ll read(){
	ll x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int n,m;
int dp[maxn][1<<maxn],a[maxn][maxn],s[maxn];
int main(void){
	// freopen("in.txt","r",stdin);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			scanf("%d",&a[i][j]);
			if(a[i][j])s[i]+=1<<(j-1);
		}
	}
	dp[0][0]=1;
	for(int i=1;i<=n;i++){
		for(int j=0;j<(1<<m);j++){
			if((j>>1)&j||(j|s[i])!=s[i])continue;
			for(int k=0;k<(1<<m);k++){
				if(!dp[i-1][k]||k&j)continue;
				dp[i][j]+=dp[i-1][k];dp[i][j]%=mod;
			}
		}
	}
	int ans=0;
	for(int i=0;i<(1<<m);i++){
		ans+=dp[n][i];
		ans%=mod;
	}
	printf("%d\n",ans);
	return 0;
}
  • 解法2
    從上述複雜度的分析可以看出,如果我們事先預處理出滿足土地限制的合法狀態,那麼狀態轉移就不是 O ( 2 m ) O(2^m) O(2m)了,而是 O ( F m ) O(F_m) O(Fm)了,所以我們可以預處理出所有合法狀態以後再進行轉移。
    複雜度: O ( n ( F m ) 2 ) < O ( n 2. 6 m ) O(n(F_m)^2)<O(n2.6^m) O(n(Fm)2)<O(n2.6m)
    程式碼:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<string>
#include<bitset>
#include<sstream>
#include<ctime>
//#include<chrono>
//#include<random>
//#include<unordered_map>
using namespace std;

#define ll long long
#define ls o<<1
#define rs o<<1|1
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define sz(x) (int)(x).size()
#define all(x) (x).begin(),(x).end()
const double pi=acos(-1.0);
const double eps=1e-6;
const int mod=1e9;
const int INF=0x3f3f3f3f;
const int maxn=13;
ll read(){
	ll x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int n,m;
int dp[maxn][1<<maxn],g[1<<maxn],a[maxn][maxn],st[maxn];
int main(void){
	// freopen("in.txt","r",stdin);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			scanf("%d",&a[i][j]);
			if(a[i][j])st[i]+=1<<(j-1);
		}
	}
	int num=0;
	for(int i=0;i<(1<<m);i++){
		if((i>>1)&i)continue;
		g[++num]=i;
	}
	dp[0][0]=1;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=num;j++){
			int s=g[j];
			if((s|st[i])!=st[i])continue;
			for(int k=1;k<=num;k++){
				int ss=g[k];
				if(!dp[i-1][ss]||ss&s)continue;
				dp[i][s]+=dp[i-1][ss];dp[i][s]%=mod;
			}
		}
	}
	int ans=0;
	for(int i=1;i<=num;i++){
		ans+=dp[n][g[i]];ans%=mod;
	}
	printf("%d\n",ans);
	return 0;
}
  • 解法3
    從相鄰兩行的關係入手,我們觀察 d p i , s dp_{i,s} dpi,s可以從哪些狀態 d p i − 1 , s ′ dp_{i-1,s'} dpi1,s轉移過來,顯然 s ′ s' s必須是 s s s的補集的子集,那麼我們轉移的時候可以直接列舉 s s s的補集的子集。
    複雜度: O ( n 3 m ) O(n3^m) O(n3m)。令 c n t ( s ) cnt(s) cnt(s) s s s中為1的位數,那麼對於每個 s s s,有 2 m − c n t ( s ) 2^{m-cnt(s)} 2mcnt(s)個子集,那麼總的列舉 s s s加其子集的數量為 ∑ i = 0 m ( m i ) 2 m − i = 3 m \sum_{i=0}^m \tbinom{m}{i}2^{m-i}=3^m i=0m(im)2mi=3m,所以總複雜度為 O ( n 3 m ) O(n3^m) O(n3m)
    程式碼:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<string>
#include<bitset>
#include<sstream>
#include<ctime>
//#include<chrono>
//#include<random>
//#include<unordered_map>
using namespace std;

#define ll long long
#define ls o<<1
#define rs o<<1|1
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define sz(x) (int)(x).size()
#define all(x) (x).begin(),(x).end()
const double pi=acos(-1.0);
const double eps=1e-6;
const int mod=1e9;
const int INF=0x3f3f3f3f;
const int maxn=13;
ll read(){
	ll x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int n,m;
int dp[maxn][1<<maxn],a[maxn][maxn],s[maxn];
int main(void){
	// freopen("in.txt","r",stdin);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			scanf("%d",&a[i][j]);
			if(a[i][j])s[i]+=1<<(j-1);
		}
	}
	dp[0][0]=1;
	for(int i=1;i<=n;i++){
		for(int j=0;j<(1<<m);j++){
			if((j>>1)&j||(j|s[i])!=s[i])continue;
			int t=((1<<m)-1)^j;
			for(int k=t;k;k=(k-1)&t){
				if(!dp[i-1][k])continue;
				dp[i][j]+=dp[i-1][k];dp[i][j]%=mod;
			}
			dp[i][j]+=dp[i-1][0];dp[i][j]%=mod;
		}
	}
	int ans=0;
	for(int i=0;i<(1<<m);i++){
		ans+=dp[n][i];
		ans%=mod;
	}
	printf("%d\n",ans);
	return 0;
}
  • 解法4
    輪廓線 d p dp dp,我們不再一行一行地轉移,這樣轉移的複雜度較大,考慮一個一個轉移。令 d p i , j , s dp_{i,j,s} dpi,j,s為處理到第 i i i行第 j j j列且暴露出來的狀態為 s s s,下圖中#集合的狀態即為 s s s
    ++++++
    +++###
    ###
    轉移方程為
    d p i , 1 , s = { d p i − 1 , m , s − { j } 1 ∈ s d p i − 1 , m , s + d p i − 1 , m , s + { j } 1 ∉ s d p i , j , s = { d p i , j − 1 , s − { j } j ∈ s & & j − 1 ∉ s d p i , j − 1 , s + d p i , j − 1 , s + { j } j ∉ s j ≠ 1 dp_{i,1,s}=\left\{\begin{aligned} dp_{i-1,m,s-\{j\}}\quad 1 \in s \\ dp_{i-1,m,s}+dp_{i-1,m,s+\{j\}}\quad 1 \notin s \end{aligned}\right. \\ dp_{i,j,s}=\left\{\begin{aligned} dp_{i,j-1,s-\{j\}}\quad j \in s \&\& j-1 \notin s \\ dp_{i,j-1,s}+dp_{i,j-1,s+\{j\}}\quad j \notin s \end{aligned}\right. \quad j \ne 1 dpi,1,s={dpi1,m,s{j}1sdpi1,m,s+dpi1,m,s+{j}1/sdpi,j,s={dpi,j1,s{j}js&&j1/sdpi,j1,s+dpi,j1,s+{j}j/sj=1
    轉移的複雜度為 O ( 1 ) O(1) O(1)
    複雜度: O ( n m 2 m ) O(nm2^m) O(nm2m)
    程式碼:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<string>
#include<bitset>
#include<sstream>
#include<ctime>
//#include<chrono>
//#include<random>
//#include<unordered_map>
using namespace std;

#define ll long long
#define ls o<<1
#define rs o<<1|1
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define sz(x) (int)(x).size()
#define all(x) (x).begin(),(x).end()
const double pi=acos(-1.0);
const double eps=1e-6;
const int mod=1e9;
const int INF=0x3f3f3f3f;
const int maxn=13;
ll read(){
	ll x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int n,m;
int dp[maxn][maxn][1<<maxn],a[maxn][maxn];
int main(void){
	// freopen("in.txt","r",stdin);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			scanf("%d",&a[i][j]);
		}
	}
	dp[0][m][0]=1;
	for(int i=1;i<=n;i++){
		for(int k=0;k<(1<<m);k++){
			if(k&1){
				if(a[i][1]==0)continue;
				dp[i][1][k]=dp[i-1][m][k^1];
			}
			else{
				dp[i][1][k]=(dp[i-1][m][k]+dp[i-1][m][k|1])%mod;
			}
		}
		for(int j=2;j<=m;j++){
			for(int k=0;k<(1<<m);k++){
				if((k>>(j-1))&1){
					if(a[i][j]==0||(k>>(j-2))&1)continue;
					dp[i][j][k]=dp[i][j-1][k^(1<<(j-1))];
				}
				else{
					dp[i][j][k]=(dp[i][j-1][k]+dp[i][j-1][k|(1<<(j-1))])%mod;
				}
			}
		}
	}
	int ans=0;
	for(int i=0;i<(1<<m);i++){
		ans+=dp[n][m][i];
		ans%=mod;
	}
	printf("%d\n",ans);
	return 0;
}