农场主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[i−1][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} (11011111)2,即 ( 223 ) 10 (223)_{10} (223)10。
就是一个进制转换。
假设一共有3列土地,则所有状态(000~111)共有7( 2 3 − 1 2^{3}-1 23−1)种可能方案。同理若有m列,枚举的范围为 [ 0 , 2 m − 1 ] [0,2^{m}-1] [0,2m−1]。
注意不能写成dp[1][i] = 1;
。因为可能i非法,而state[i]合法,下面也一样。
因为只有一行,所以一种状态只会对应一种方案。
第一行已经求出,所以从第二行开始枚举。
state[j] != (state[j] & mp[i])
和state[h] != (state[h] & mp[i - 1])
都是判断方案与数据是否抵触。至于为什么这么写,可以想一下。
未完待续…