状压 dp 题目
Link
& 同为 1 则为 1,否则为 0。求交集。
| 一个为 1 则 1,否则为 0。求并集。
^ 相同为 0,不同为 1。
<< 每一位向左移动,空出的补零。
>> 删掉最右边的几位,左边的数向右边补位,空出的补零。
n 个 1
(1 << n) - 1
S 第 i 位是否为 1
if (S & (1 << (i - 1)))
S 第 i 位变成 1
S |= 1 << (i - 1)
S1 - S2
S1 & (~S2)
S 的补集
S ^ ((1 << n) - 1)
S2 是不是 S1 的子集
if (S1 & S2 == S1)
S 有几个 1
int get(int n) {
int ans = 0;
while (n) {
ans++;
n &= (n - 1);
}
return ans;
}
枚举 S 的二进制子集
for(int i = S; i; i= S & (i - 1))
Corn Fields
左右不相邻,上下不相邻,土地肥沃。
设
表示在前
行中(包括
)且 第
行状态为
的最大方案数。我们的
是行,
是列的状态。1
和 0
的意义与题目相同。我们先不看转移方程,因为状压 dp 几乎都不难在方程上。
用二进制数存储数据也是模板了,就不细讲了。每次右移后右边多出个 0
,然后加上去。
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int a; scanf("%d", &a);
f[i] = (f[i] << 1) + a; //储存地图的方法
}
}
先考虑解决左右不相邻。我们对于一个状态,如何知道它是否有相邻的两个 1
呢?
for (int i = 0; i < (1 << n); i++)
g[i] = (!(i & (i << 1))) && (!(i & (i >> 1)));
模拟一下:对于一个二进制数,假设为 110
即为 0110
,左移后为 1100
,与运算后为 0100
即为
。对其右移后为 0110
,与运算后是 0110
为
, 因为
和
非运算 后都是
,所以就有两个连续的
。
对于一个二进制数 0001
,左移后为 0010
,与运算后结果为
,进行右移后为 0000
,与运算后还为
。表达式的值也为
了。
你可以那么理解:如果有两个连续的 1
,那么右移之后至少有一个 1
在同一位置上。左移同理。
右移前: 11
右移后: 11
那么我们就解决了左右不相邻的问题。上下不相邻呢?如果两个位都为 1
与运算后才为 1
。我们只要保证 状态
与 上一行的状态
进行与操作之后还不等于
即可。
土地肥沃也很好解决,只要在枚举的第
列的状态
中第
位是 1
,那么我们存的地图
的第
位必须也是 1
。你会发现这个用与运算就可以了。
(j & f[i]) == j
那么该说如何转移了,不要忘记判断枚举的状态 是否合法哦。统计答案时枚举最后一行的状态即可。
#include <bits/stdc++.h>
using namespace std;
const int p = 100000000, m = (1 << 12) + 12;
int m, n, a[20][20], f[20], dp[20][m];
bool book[m];
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
int a; scanf("%d", &a);
f[i] = (f[i] << 1) + a; //储存地图的方法
}
for (int i = 0; i < (1 << m); i++)
book[i] = (!(i & (i << 1))) && (!(i & (i >> 1)));//最重要的操作。
dp[0][0] = 1;
for (int i = 1; i <= n; i++) //行
for (int j = 0; j < (1 << m); j++) //状态
if (book[j] && ((j & f[i]) == j)) //没有左右相邻并且土地肥沃
for (int k = 0; k < (1 << m); k++) //上一行状态
if ((k & j) == 0)
dp[i][j] = (dp[i][j] + dp[i - 1][k]) % p;
int ans = 0;
for (int i = 0; i < (1 << m); i++) //统计答案
ans = (ans + dp[n][i]) % p;
printf("%d\m", ans);
return 0;
}