Luogu P1879 [USACO06NOV]玉米田Corn Fields

版权声明:转载注明出处,部分带(坑)文章谢绝转载。 https://blog.csdn.net/linjiayang2016/article/details/83864363

题意

给出 n m n*m 的矩阵,选出若干个 0 0 变成 2 2 ,使得没有两个 2 2 有公共边,求方案数量。
数据范围 1 n , m 12 \quad1\leqslant n,m \leqslant 12


题解

看到这么小的数据范围,显然可以状态压缩,考虑状压缩DP。
f [ i ] [ j ] f[i][j] 表示考虑前 i i 行且第 i i 行状态为 j j (压缩)的方案数量。
枚举 k k 表示上一行的状态(压缩),则有: f [ i ] [ j ] = k j f [ i 1 ] [ k ] f[i][j]=\sum\limits_{k能合法转移到j} f[i-1][k]
最后的答案为 k f [ n ] [ k ] \sum_k f[n][k] ,时间复杂度 Θ ( m 4 n ) \Theta(m4^n)
代码

#include <bits/stdc++.h>
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] 表示考虑到 i i j j 列的格子,此时的轮廓线状态为 k k (压缩)。
例如:下表 ( ) (*) 处表示状态 f [ 2 ] [ 2 ] [ 101 0 ( 2 ) ] f[2][2][1010_{(2)}]

1 1 0 0 1 1
0 0 ( ) (*)

这个定义依然很好转移,处理好边界即可,而且可以用滚动数组优化掉前维。
最后的答案是 k f [ n ] [ m ] [ k ] \sum_k f[n][m][k] ,这个算法的时间复杂度为 Θ ( n m 2 n ) \Theta(nm2^n) ,运算量约为六十万次,能轻易AC。
代码

#include<bits/stdc++.h>
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;
}

猜你喜欢

转载自blog.csdn.net/linjiayang2016/article/details/83864363