状压DP2 P1879 [USACO06NOV]Corn Fields G

P1879 [USACO06NOV]Corn Fields G

题目地址:https://www.luogu.com.cn/problem/P1879

题目内容:农场主John新买了一块长方形的新牧场,这块牧场被划分成M行N列(1 ≤ M ≤ 12; 1 ≤ N ≤ 12),每一格都是一块正方形的土地。John打算在牧场上的某几格里种上美味的草,供他的奶牛们享用。

遗憾的是,有些土地相当贫瘠,不能用来种草。并且,奶牛们喜欢独占一块草地的感觉,于是John不会选择两块相邻的土地,也就是说,没有哪两块草地有公共边。

John想知道,如果不考虑草地的总块数,那么,一共有多少种种植方案可供他选择?(当然,把新牧场完全荒废也是一种方案)

题目解析:同样是状态压缩,和     状态压缩入门 P2622 关灯问题II 不同之处在于,维度由1维变为2维。问题考虑当前行,当前行的理论方案数目为2^n种。用dp[i][j]来表示第i行选用方案j的可行方案数目(dp[i][j]由dp[i-1]行的可行方案转变过来。)

dp[i][j]的值需要先进行规则判断:

1、行内判断:①方案j是否满足没有相邻的草地(连续的11),判断方式 (!(j&(j>>1)))&(!(j&(j<<1))),其实就是将j和j左移一位和右移一位进行比较,结果必须为0;②是否在贫瘠土地种草,用一个数组g[i] 代表第i行的输入状态,(j&g[i])==j,其实就是将j中贫瘠的土地设为0,然后和原来的j比较,判断得出是否在贫瘠土地上种草。

2、与上一行比较:k为i-1行的某个方案,如果(k&j)==0,则上一行的k状态可以转移到本行的j状态,dp[i][j] = (dp[i][j]+dp[i-1][k])%mod; 加上上一行的方案

除此之外,需要注意的地方是初始状态可以设置一个dp[0][0]=1,这样可以给第一行一个方案初值,很有效,含义是初始行不种草(题目认为这也是一种方案),方案数为1。

#include<iostream>
using namespace std;
// checkRow: 不考虑贫瘠土地的理论情况下,行内可行方案
// orgin: 土地原状
// dp:递归数组,dp[i][j]存储了自上至下到达[i,j]位置可行方案数
const int mod = 1e8;
int m,n,checkRow[1<<15],orgin[20],dp[20][1<<15];
int main(){
    cin>>m>>n;
    int temp; // 用于记录读入的每个数字
    for(int i=1;i<=m;i++){
        // 读入每一行的原始状态
        for(int j=1;j<=n;j++){
            cin>>temp;
            orgin[i] = (orgin[i]<<1) + temp; //依次向前左移动一位
        }
    }
    dp[0][0] = 1; // 全都不种
    // 不考虑贫瘠土地的行列理论可行方案
    for(int i=0;i<(1<<n);i++) checkRow[i] = (!(i&(i>>1)))&&(!(i&(i<<1))); // 与左移一位和右移一位比较
    for(int i=1;i<=m;i++){
        for(int j=0;j<(1<<n);j++){
            // 首先要行内合适
            if(checkRow[j]&&((j&orgin[i])==j)){ // 理论可行,且没有占用贫瘠土地
                // 把当前状态和上一行的所有状态进行比较
                for(int k=0;k<(1<<n);k++){
                    if((k&j)==0) dp[i][j] = (dp[i][j]+dp[i-1][k])%mod; // 上一行的k方案可转移到当前行的j方案
                }
            }
        }
    }
    int res = 0;
    // 累加最后一个行可行方案即可
    for(int i=0;i<(1<<n);i++) res = (res+dp[m][i])%mod;
    cout<<res<<endl;
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/zhuanghuaijilie/p/12804419.html