刷题系列——状压 dp

状压 dp 题目

Link

&	同为 1 则为 1,否则为 0。求交集。
|	一个为 11,否则为 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

左右不相邻,上下不相邻,土地肥沃。

d p i , j dp_{i,j} 表示在前 i i 行中(包括 i i )且 第 i i 行状态为 j j 的最大方案数。我们的 i i 是行, j j 是列的状态。10 的意义与题目相同。我们先不看转移方程,因为状压 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 即为 4 4 。对其右移后为 0110,与运算后是 0110 6 6 , 因为 4 4 6 6 非运算 后都是 0 0 ,所以就有两个连续的 1 1

对于一个二进制数 0001,左移后为 0010,与运算后结果为 0 0 ,进行右移后为 0000,与运算后还为 0 0 。表达式的值也为 1 1 了。

你可以那么理解:如果有两个连续的 1,那么右移之后至少有一个 1 在同一位置上。左移同理。

右移前: 11
右移后:  11

那么我们就解决了左右不相邻的问题。上下不相邻呢?如果两个位都为 1 与运算后才为 1。我们只要保证 状态 j 0 j_0 与 上一行的状态 j j 进行与操作之后还不等于 j 0 j_0 即可。

土地肥沃也很好解决,只要在枚举的第 i i 列的状态 j j 中第 x x 位是 1,那么我们存的地图 f i f_i 的第 i i 位必须也是 1。你会发现这个用与运算就可以了。

(j & f[i]) == j

那么该说如何转移了,不要忘记判断枚举的状态 k k 是否合法哦。统计答案时枚举最后一行的状态即可。

d p i , j = ( d p i , j + d p i 1 , k ) m o d    p dp_{i,j}=(dp_{i,j}+dp_{i-1, k}) \mod p

#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;
}

猜你喜欢

转载自blog.csdn.net/qq_39984146/article/details/107726595