【学术篇】状态压缩动态规划——POJ3254/洛谷1879 玉米田Corn Field

我要开状压dp的坑了。。直播从入门到放弃系列。。

那就先拿一道状压dp的水题练练手吧。。

然后就找到了这一道。。这道题使我清醒地认识到阻碍我的不是算法,而是视力= =

传送门:
poj:http://poj.org/problem?id=3254
luogu:https://www.luogu.org/problem/show?pid=1879.233333 (雾

题目大意:

n*m的01矩阵上放棋子(牛),不能放到相邻格子,问方案数。(啥也不放算一种方案(然而我视力好到看到这一点了))

题目分析:

都说了状压dp 水题肯定要用仙人图上在线分支定界启发式带花树上下界最小费用流解决问题啊~(什么鬼)

我们可以看出,我们可以通过i-1行的放法转移出第i行的放法,所以这题是dp无疑。状态转移方程:

第i行状态为j(j合法):
f[i][j]=sigma(f[i-1][k]) (k与j不冲突)

然而j是一种状态而不是一个数字。所以这就是本题不同于传统dp的地方,我们要状压

嗯,状压,什么是状压? 状态压缩
那什么是状态压缩?
以本题为例,我们发现n,m的范围很小(其实有一维的范围很小就可以O(∩_∩)O)
而每一个格子上都只有放牛和不放牛两种选择,所以……
既然是学信息的,我们可以用二进制来表示啊~

我们可以用不超过12位二进制来表示当前行中的状态,从而完成了压缩。。 还有可爱的位运算为伴哦~

大体来说就是这样。。

如何筛选合法状态?

题目中,对于同一行中相邻的情况,我们可以用(i&(i-1))解决..
对于行之间相邻的情况,把两行按位与一下即可。。

每一行枚举复杂度不会爆炸吗?

(本题不存在此担心,2^12^2勉强能跑过,但有些题是30左右就hold不住了..)
这个放心即可。。我们可以先预处理。。然后发现可枚举的状态其实是十分有限的..

预处理大概是长介个样纸:

for(int i=0;i<1<<m;i++)
    if(!(i&(i<<1))){
        op[cnt++]=i;
    }

经实测,在2^12=4096个状态中,能在1行中合法存在的只有377个。。(要算错了可以来纠正我_ (:з」∠) _反正我日常算错。。不过算错应该不会差多少)

总之不会爆炸,而且还跑得很快就对了~~

啰嗦了半天,还是上代码吧。。

代码实现

#include <cstdio>
#include <cstring>

int op[380],cnt,n,m,ans;
int f[13][380],za[13];

void dp(){
    for(int i=0;i<1<<m;i++)
        if(!(i&(i<<1))){
            op[cnt]=i;
            if(!(i&za[0])) f[0][cnt]=1;
            cnt++;
        }
    for(int i=1;i<n;i++)
        for(int j=0;j<cnt;j++){
            if(op[j]&za[i]) continue;
            for(int k=0;k<cnt;k++)
                if(!(op[j]&op[k])) 
                    f[i][j]+=f[i-1][k];
        }    
}

int main(){    
    scanf("%d%d",&n,&m);
    ans=0; cnt=0;
    for(int i=0;i<n;i++)
        for(int j=0;j<m;j++){
            int k; scanf("%d",&k);
            if(!k) za[i]|=(1<<j);
        }dp(); 
    for(int i=0;i<cnt;i++) ans=(ans+f[n-1][i])%100000000;
    printf("%d
",ans);
}

大概就这样吧。。

文末彩蛋
为什么我说这题卡视力了呢?
因为我前几遍硬是没看见取模~
还到luogu下了一次数据才看出来。。
我多半是完了_ (:з」∠) _
原文地址:https://www.cnblogs.com/enzymii/p/8412149.html