Luogu P1879 [USACO06NOV]玉米田Corn Fields 【状压dp模板】
前置知识
位运算常用方法
判断一个数字x二进制下第i位是不是1
- return ((1<<(i−1))&x)?true:false
将一个数字x二进制下第i位更改成1。
- x=x|(1<<(i−1))
把一个数字二进制下最靠右的第一个1去掉。
- x=x&(x−1)
判断一个数是否为2的幂
- return x & (x-1) == 0
找到最右边的1(也就是lowbit)
- return x & (-x)
直接看代码
#include<cstdio> #include<iostream> #include<cmath> #include<cstring> #define MogeKo qwq using namespace std; const int mod = 1e9; int n,m,x,ans; int a[13],f[13][1<<12+5],g[1<<12+5]; int main() { scanf("%d%d",&m,&n); for(int i = 1; i <= m; i++) for(int j = 1; j <= n; j++) { scanf("%d",&x); a[i] = (a[i]<<1)|x; } for(int i = 0; i < (1<<n); i++) g[i] = (!((i<<1)&i) && !((i>>1)&i)); f[0][0] = 1; for(int i = 1; i <= m; i++) for(int j = 0; j < (1<<n); j++) if(g[j] && ((j&a[i]) == j)) for(int k = 0; k < (1<<n); k++) if((k & j) == 0) f[i][j] = (f[i][j] + f[i-1][k])%mod; for(int i = 0; i < (1<<n); i++) ans = (ans + f[m][i])%mod; printf("%d\n",ans); return 0; }
限制条件:
- 土地本身可用
- 左右不相邻
- 上下不相邻
首先读入每一格土地状态,对于第i行,用a[i]表示每一块土地是否可用;
a[i]用一个二进制数,每读一个,将原来的a[i]左移一位,并加上当前的一位。
显然,对于一行n列的土地,这一行的状态一共有(1<<n)种(比如000,001,010,011,100,101,110,111)
那么,从0枚举到(1<<n)-1,就能枚举到每一种情况。
如何判断是否左右相邻?如果一个状态是左右不相邻的,那将它像左(或右)移一位后,一定是交错的,也就是说没有移动前后都为1的数位。比如,
01001
10010
所以,对于每一个状态i,分别判断i&(i<1)和i&(i>>1)是否都为0,是则合法,g[i] = 1.
如何判断上下相邻?
枚举每一行i及状态j。
如果状态j合法,必须有则g[j]==1,且在j中不能包含不可用的土地。
也就是说,j中包含的每一块土地,都包含在这一列的合法的集合a[i]中,即(j&a[i]) == j.
枚举上一行的状态k。
k,j不能存在上下相邻的土地,即(k&j)==0.
将上一行的状态k加入这一行的状态j中,即f[i][j] += f[i-1][k].
最后得到了最后一行每种状态下的方案数,累加即可。
注意:初始化f[0][0] = 1