解题报告:洛谷P1879 [USACO06NOV]玉米田Corn Fields

题目描述

农场主John新买了一块长方形的新牧场,这块牧场被划分成M行N列(1 ≤ M ≤ 12; 1 ≤ N ≤ 12),每一格都是一块正方形的土地。遗憾的是,有些土地相当贫瘠,不能用来种草。并且,奶牛们喜欢独占一块草地的感觉,于是John不会选择两块相邻的土地,也就是说,没有哪两块草地有公共边。

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

输入输出格式

输入格式:

第一行:两个整数M和N,用空格隔开。

第2到第M+1行:每行包含N个用空格隔开的整数,描述了每块土地的状态。第i+1行描述了第i行的土地,所有整数均为0或1,是1的话,表示这块土地足够肥沃,0则表示这块土地不适合种草。

输出格式:

一个整数,即牧场分配总方案数除以100,000,000的余数。


分析

状态转移方程

f [ i ] [ j ] = ∑ j = 0 k f [ i − 1 ] [ j ] ( 方 案 j 保 证 合 法 ) f[i][j] = \sum_{j=0}^{k}{f[i-1][j]}(方案j保证合法) f[i][j]=j=0kf[i1][j]j

f [ i ] [ j ] f[i][j] f[i][j]的含义:在第i行的状态(种草方案)为j时的总方案数。

在考虑状态转移方程时,我们要思考这道题最重要的自变量是什么。本题中,最重要的自变量为某处是否要种草,而它的决定因素只有三个:

  • 左右是否有其他草地相邻;
  • 上方是否有草地相邻(下方的还没有算出来所以不用考虑);
  • “贫瘠的”土地上不能种草,即考虑是否与数据相抵触。

先来看第一个因素:

每一行内不允许出现相邻草地的情况,如01010110就是不合法的。如何来判断这种情况?

结合按位与的逻辑与合法情况的特征,假如将合法情况(如01010101或001001001)左移或右移一位,得到的新数与原数进行按位与操作,结果为0,若是非法情况,则结果为非0,据此判断合法情况。

for (int i = 0;i <= (1 << m) - 1;i++)
	if (!(i & (i << 1))) //作者只考虑了左移的情况,可能是数据太水了
		state[k++] = i;

第二个因素:

假设上一行的方案为10101010,则下一行不能是01010010。将这两个数进行按位与运算,结果为非0。

假如下一行为01010101,这就是一个合法方案。结合按位与的逻辑,判断方法如下:

if (!(state[h] & state[j]))
	dp[i][j] += dp[i - 1][h] % MOD;

第三个因素:

假设某一行数据为[1 0 0],则方案[1 0 1]是非法的,因为第三格(1)不能种草。100 & 101 = 1.

同理,可以将某一行的数据与方案进行按位与运算,结果非零则不合法,否则合法。


解决问题

作者照着别人的程序打了一遍后,发现还是不理解,经过反复的思考,他终于写出了自己的代码:

int main()
{
    scanf("%d%d",&n,&m);
    for (int i = 1;i <= n;i++)
        for (int j = 1;j <= m;j++)
        {
            scanf("%d",&mi);
            mp[i] <<= 1;
            mp[i] += mi;
        }//状态压缩。
        
    for (int i = 0;i <= (1 << m) - 1;i++)
        if (!(i & (i << 1)))
            state[k++] = i;
            //找到一行中的合法情况。
            
    for (int i = 0;i < k;i++)
        if (state[i] == (state[i] & mp[1]))
            dp[1][state[i]] = 1;
            //初始化。动规都是要赋初值的对不对qwq
            
    for (int i = 2;i <= n;i++)
        for (int j = 0;j < k;j++)
        {
            if (state[j] != (state[j] & mp[i])) continue;
            for (int h = 0;h < k;h++)
            {
                if (state[h] != (state[h] & mp[i - 1])) continue;
                if (!(state[h] & state[j]))
                {
                    dp[i][state[j]] += dp[i - 1][state[h]];
					dp[i][state[j]] %= MOD;
				}
            }
        }//dp过程
        
    for (int i = 0;i < k;i++)
    {
        ans += dp[n][state[i]];
        ans %= MOD;
    }//求解
    printf("%d",ans);
}

代码解释

状态压缩

假设有状态[1 1 0 1 1 1 1 1],每读入一位,先将前面的左移一位,再在末尾加上新值。状压的结果为 ( 11011111 ) 2 (11011111)_{2} 110111112,即 ( 223 ) 10 (223)_{10} 22310

就是一个进制转换。

找到一行内合法情况

假设一共有3列土地,则所有状态(000~111)共有7( 2 3 − 1 2^{3}-1 231)种可能方案。同理若有m列,枚举的范围为 [ 0 , 2 m − 1 ] [0,2^{m}-1] [0,2m1]

初始化

注意不能写成dp[1][i] = 1;。因为可能i非法,而state[i]合法,下面也一样。

因为只有一行,所以一种状态只会对应一种方案。

dp过程

第一行已经求出,所以从第二行开始枚举。
state[j] != (state[j] & mp[i])state[h] != (state[h] & mp[i - 1])都是判断方案与数据是否抵触。至于为什么这么写,可以想一下。

未完待续…

你可能感兴趣的:(解题报告)