P1879 [USACO06NOV]Corn Fields G
阿新 • • 發佈:2021-01-20
技術標籤: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)
(1≤n,m≤12)
題解:
這是一道狀壓 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
複雜度: O ( n 2 m F m ) < O ( n 3. 2 m ) O(n2^mF_m)<O(n3.2^m)
程式碼:
#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'} dpi−1,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)} 2m−cnt(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)2m−i=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={dpi−1,m,s−{j}1∈sdpi−1,m,s+dpi−1,m,s+{j}1∈/sdpi,j,s={dpi,j−1,s−{j}j∈s&&j−1∈/sdpi,j−1,s+dpi,j−1,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;
}