给出 n ∗ m n*m n∗m 的矩阵,选出若干个 0 0 0 变成 2 2 2,使得没有两个 2 2 2 有公共边,求方案数量。
数据范围 1 ⩽ n , m ⩽ 12 \quad1\leqslant n,m \leqslant 12 1⩽n,m⩽12
看到这么小的数据范围,显然可以状态压缩,考虑状压缩DP。
设 f [ i ] [ j ] f[i][j] f[i][j] 表示考虑前 i i i 行且第 i i i 行状态为 j j j (压缩)的方案数量。
枚举 k k k 表示上一行的状态(压缩),则有: f [ i ] [ j ] = ∑ k 能 合 法 转 移 到 j f [ i − 1 ] [ k ] f[i][j]=\sum\limits_{k能合法转移到j} f[i-1][k] f[i][j]=k能合法转移到j∑f[i−1][k]
最后的答案为 ∑ k f [ n ] [ k ] \sum_k f[n][k] ∑kf[n][k],时间复杂度 Θ ( m 4 n ) \Theta(m4^n) Θ(m4n)
代码
#include
using namespace std;
#define mod 100000000
int m, n, f[15][4100], F[15], field[15][15];
bool check[4100];//只考虑本行是否合法
int main() {
scanf("%d%d",&m,&n);
for (int i=1;i<=m;i++)
for (int j=1;j<=n;j++)
scanf("%d",&field[i][j]);
for (int i=1;i<=m;i++)//压缩行
for (int j=1;j<=n;j++)
F[i]=(F[i]<<1)+field[i][j];
for (int i=0;i<(1<<n);i++)
check[i]=((i&(i<<1))==0) && ((i&(i>>1))==0);
f[0][0]=1;
for(int i=1;i<=m;i++)
for(int j=0;j<(1<<n);j++)
if(check[j] && ((j&F[i])==j))
for(int k=0;k<(1<<n);k++)
if((k & j)==0)
f[i][j]=(f[i][j]+f[i-1][k])%mod;
int ans=0;
for(int i=0;i<(1<<n);i++)
ans+=f[m][i],ans%=mod;
printf("%d",ans);
return 0;
}
上面的方法能很快地通过本题,但根据本题的数据范围,计算次数约为两亿次左右,有些玄学,因此我们需要更高效的算法。考虑更换方程定义。
设 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k] 表示考虑到 i i i 行 j j j 列的格子,此时的轮廓线状态为 k k k (压缩)。
例如:下表 ( ∗ ) (*) (∗) 处表示状态 f [ 2 ] [ 2 ] [ 101 0 ( 2 ) ] f[2][2][1010_{(2)}] f[2][2][1010(2)]
1 1 1 | 0 0 0 | 1 1 1 | |
---|---|---|---|
0 0 0 | ( ∗ ) (*) (∗) |
这个定义依然很好转移,处理好边界即可,而且可以用滚动数组优化掉前两维。
最后的答案是 ∑ k f [ n ] [ m ] [ k ] \sum_k f[n][m][k] ∑kf[n][m][k],这个算法的时间复杂度为 Θ ( n m 2 n ) \Theta(nm2^n) Θ(nm2n),运算量约为六十万次,能轻易AC。
代码
#include
using namespace std;
#define mod 100000000
#define maxn 13
int n,m,a[maxn][maxn];
int f[2][1<<maxn],cur;
int main(void){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&a[i][j]);
f[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
memset(f[cur^=1],0,sizeof f[cur]);
for(int k=0;k<(1<<m);k++){
int up=(k&(1<<(j-1))),left=0;//拆出上面和左边的位置
if(j>1) left=(k&(1<<(j-2)));
if(i==1&&up!=0) continue;//第一行的上面不可能被选择
if(j==1&&left!=0) continue;//第一列的左边不可能被选择
if(left&&up)//左边和上面都选了,只能不选
f[cur][k^(1<<j-1)]=(f[cur][k^(1<<j-1)]+f[cur^1][k])%mod;
else if(up)//上面选了,只能不选
f[cur][k^(1<<j-1)]=(f[cur][k^(1<<j-1)]+f[cur^1][k])%mod;
else if(left)//左边选了,只能不选
f[cur][k]=(f[cur][k]+f[cur^1][k])%mod;
else if(a[i][j]==0)//不为0,只能不选
f[cur][k]=(f[cur][k]+f[cur^1][k])%mod;
else{
f[cur][k]=(f[cur][k]+f[cur^1][k])%mod;//选
f[cur][k^(1<<j-1)]=(f[cur][k^(1<<j-1)]+f[cur^1][k])%mod;//不选
}
}
}
int ans=0;
for(int i=0;i<(1<<m);i++)//统计答案
ans=(ans+f[cur][i])%mod;
printf("%d",ans);
return 0;
}