DP-状态压缩-luogu1879-玉米田

问题描述

农夫有一块m ∗ * n的等分网格田地,他想在田地上种草。要求种草的格子田必须互不相连,现给出每块格子田各自的种植性质(能种(1)/不能种(0)),求种草方案总数。(1=<n,m<=12

问题分析

  1. 问题的规模很小,这让人想到状态压缩
  2. 我们分别为每一排的情况做一个状态压缩,原因如下:
    1. 对于单独的一块土地来说,只有种与不种两种选择。
    2. 状态压缩一般直接用于一维情况。
    3. 状态压缩总是需要枚举所有状态的:使用一维压缩,需要位数最多为12,则总状态数最多为 2 12 = 4 k 2^{12}=4k 212=4k;如果使用二维压缩,需要位数最多为 12 ∗ 12 = 144 12*12=144 1212=144,总状态数最多为 2 144 2^{144} 2144,过于巨大,显然不可取
  3. 对于任何一排的某种种草方案,方案可行仅当
    1. 方案本身可行,即不存在行上的连续种草
    2. 方案与土地性质匹配,即不可种草的土地不种草
    3. 方案与上下行协调,即不存在列上的连续种草
  4. 为有效实现以上思想,定义数据如下:
    1. F[maxn],F[i]表示第i行的田地性质(二进制状态压缩)
    2. f[maxn][1<<maxn],f[i][j]表示在第i行种草方案为j的情况下 利用了前i行(包括第i行)田地的种草方案数
    3. ok[1<<maxn],a[i]表示单行方案i的单行合法性(有无连续种草)
  5. 在实现时,涉及到的位操作如下:
    1. F[i]=(F[i]<<1)+a[i][j] 将第i行的数字转成二进制位,如 101 → \to 5,110 → \to 6
    2. (j&F[i])==j 判断第i行的种草方案j与土地性质F[i]是否匹配,如j=101,F[i]=111,则(j&F[i])=101;j=101,F[i]=011,则(j&F[i])=001;
    3. !(j&k) 判断第j行和第k行是否存在列连续种草,如j=101,k=010,则j&k=000;j=101,k=110,则j&k=100;
  6. 动态规划状态转移式
    f [ i ] [ j ] = ∑ k f [ i − 1 ] [ k ] f[i][j]=\sum_{k}f[i-1][k] f[i][j]=kf[i1][k]
    式中,j为第i行的种草方案,k为第i-1行的种草方案,j与k必须相互协调
  7. 关于取模,采取边算边取,这样可仍然选用int:$(a+b+c)\ %\ p== ((a+b)\ %\ p+c)\ %\ p $

AC代码

#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;
const bool debug=false;//用于控制debug测试信息的输出
const int maxn=12,M=1e8;

int n,m,ans=0;
int a[maxn][maxn];
int F[maxn],f[maxn][1<<maxn];//F[i]表示第i行的田地性质(二进制状态压缩),f[i][j]表示在第i行种草方案为j的情况下 利用了前i行(包括第i行)田地的种草方案数
bool ok[1<<maxn];//a[i]表示单行状态i的合法性(有无连续种草)

int main()
{
    //输入数据
    scanf("%d%d",&m,&n);
    for(int i=0;i<m;i++)
        for(int j=0;j<n;j++)
            scanf("%d",&a[i][j]);
    //对每一排的田地性质进行二进制状态压缩
    for(int i=0;i<m;i++)
        for(int j=0;j<n;j++)
            F[i]=(F[i]<<1)+a[i][j];
    //预判段每种种草方案的合法性,初始化第0行方案数
    for(int i=0;i<(1<<n);i++){
        if(!(i&(i<<1)) && !(i&(i>>1))){
            ok[i]=true;
            if((i&F[0])==i)f[0][i]=1;
        }
        if(debug){
            printf("[%d]=%d ",i,ok[i]);
            if(i==(1<<n)-1){
                printf("\n");
                //for(int i=0;i<(1<<n);i++)
                //    printf("f[0][%d]=%d ",i,f[0][i]);
                printf("\n");
            }
        }
    }
    //动态规划
    for(int i=1;i<m;i++){
        for(int j=0;j<(1<<n);j++){
            if(ok[j] && (j&F[i])==j){
                for(int k=0;k<(1<<n);k++){
                    if(ok[k] && (k&F[i-1])==k && !(j&k))
                        f[i][j]=(f[i][j]+f[i-1][k])%M;
                }
            }
        }
    }

    if(debug){
        for(int i=0;i<m;i++){
            for(int j=0;j<(1<<n);j++)
                printf("f[%d][%d]=%d ",i,j,f[i][j]);
            printf("\n");
        }
    }
    //统计信息,输出结果
    for(int j=0;j<(1<<n);j++)
        ans=(ans+f[m-1][j])%M;
    printf("%d\n",ans);

    return 0;
}

参考博客

  1. https://www.luogu.org/blog/Owencodeisking/solution-p1879 (本题洛谷题解)
  2. https://www.cnblogs.com/Tony-Double-Sky/p/9283254.html (状态压缩知识探究)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值