状态压缩的基本操作

数量规模不超过20时 要存储每个状态取或不取的状态时可以用状态压缩。

所谓状态压缩,就是用二进制数表示集合 用1代表有该元素,0代表没有该元素

{100101}=37  表示集合中有第1,3,6个元素。  用状压dp存储状态消耗的空间是2^n

例题1:

有一个n*m的矩阵 其中有的格子可以选,有的不能选, 且不能选中相邻的格子

问: 最多能选定多少个格子?

例如下面的格子, 可选的标为1,不可选的标为0

选择的方案是  共3个可选位置

 

解决方案: 自上而下逐行的选择格子,每行的格子选择限定条件:1 这行原本的限制可选位置、2 这行选的格子不能相邻、3 这行的格子不能和上一行的格子相邻

 这一行的选择情况可以用二进制表示,设这行选了now , 这行的限定条件是 lim , 上一行选了 pre 

对于条件1: lim | now == lim

  条件2: now&(now>>1) ==0

  条件3: now&pre==0

完整代码

 1 const int MAX_N = 20;
 2 const int MAX_M = 20;
 3 int state[MAX_N + 1];
 4 int dp[MAX_N + 1][1 << MAX_M];
 5 
 6 bool not_intersect(int now, int prev) {
 7     return (now & prev) == 0;
 8 }
 9 
10 bool fit(int now, int flag) {
11     return (now | flag) == flag;
12 }
13 bool ok(int x) {
14     // 行内自己是否相交
15     return (x & (x / 2)) == 0;
16 }
17 int count(int now) {
18     int s = 0;  // 统计 now 的二进制形式中有多少个 1
19     while (now) {
20         s += (now & 1);  // 判断 now 二进制的最后一位是否为 1,如果是则累加
21         now >>= 1;  // now 右移一位
22     }
23     return s;
24 }
25 
26 int main() {
27     int n, m;
28     cin >> n >> m;
29     // 初始化所有数组
30     for (int i = 1; i <= n; ++i) {
31         for (int j = 0; j < m; ++j) {
32             int flag;
33             cin >> flag;
34             state[i] |= (1 << j) * flag;  // 将 (i,j) 格子的状态放入 state[i] 中,state[i] 表示第 i 行的可选格子组成的集合
35         }
36     }
37     for (int i = 1; i <= n; ++i) {
38         for (int j = 0; j < (1 << m); ++j) {  // 枚举当前行的状态
39             if (!ok(j) || !fit(j, state[i])) {  // 如果当前行状态不合法则不执行后面的枚举
40                 continue;
41             }
42             int cnt = count(j);  // 统计当前行一共选了多少个格子
43             for (int k = 0; k < (1 << m); ++k) {
44                 if (ok(k) && fit(k, state[i - 1]) && not_intersect(j, k)) {  // 判断前一行是否合法和当前行和前一行的方案是否冲突
45                     dp[i][j] = max(dp[i][j], dp[i - 1][k] + cnt);  // 更新当前行、当前状态的最优解
46                 }
47             }
48         }
49     }
50     int ans = 0;  // 保存最终答案
51     for (int i = 0; i < (1 << m); ++i) {
52         ans = max(ans, dp[n][i]);  // 枚举所有状态,更新最大值
53     }
54     cout << ans << endl;
55     return 0;
56 }
View Code

统计二进制数中1的数目

int count(int x)
{
    int num=0;
    while(x)
    {
       num+=x&1;
        x>>=1;
    }
    return num;
}

例子2:  消除回文串

给定一个字符串s  可以从中消除回文串, 回文串不必连续,问最少多少次可以消除整个串

状态d[ i ] 表示消除字符串i 需要的最少步数。

状态转移: if( i串不是回文串)  d[ i ] = d [ j ] + d [ j^i];  else  d[ i ] = 1;   // j  是i串的子集 ,  j^i 是  j的补集

for (int t = 1; t < (1 << n); t++) {  // 枚举当前状态
     if(IsPalindrome(t) ) dp[t]=1 ;  // 判断当前状态是否是回文,如果是回文则步骤数为 1
    else {
        for(int i = t; i; i = (i - 1) & t) { // 枚举 t 的所有子集
            dp[t] = min(dp[t], dp[i] + dp[t ^ i]);  // 更新当前状态的解的最小值
        }
    }
}
printf("%d\n", dp[(1 << n) - 1]);  // 输出最终答案

 

//枚举子集  for(int i = t ; i ; i = (i - 1) & t ) 

posted @ 2017-10-17 21:21  小螺号打豆豆  阅读(340)  评论(0编辑  收藏  举报