2019牛客暑期多校训练营(第五场)E independent set 1(状压DP)

题目链接
题意:给一个无权图(可以有环),求每个子图最大独立集大小的和。

思路:设dp[s]为子图点集为 s (二进制下)的最多独立点数量,对于dp[s],我们找到 i 的最右边1的位置k(其他的1转移也可以),删除最右边的1得到点集_s,对于dp[s],要么来自dp[_s],(代表k点与_s中的某个点相邻,于是k点没有贡献),或者来自删掉与k点相邻的点的集合使k点有贡献Dp[_s ^ (E[w] & _s)] + 1。
于是状态转移方程为
Dp[s] = max(Dp[_s], (unsigned char) (Dp[_s ^ (E[w] & _s)] + 1));
dp使用char 数组的原因是因为dp[i]很小,用char表示0~255就行了,不会被卡内存。
具体细节见代码。

看了排行榜前排的代码,感觉大佬很多细节,处理的很好。
这些操作可以让你处理有关二进制的题,写代码更方便:
不过注意一下__builtin好像只能用于int 大小的数据。

  1. __builtin_ctz(x); // 求x的二进制数末尾0的个数
  2. __builtin_clz(x); // 求x的二进制数前导0的个数
  3. __builtin_popcount(x); // 求x的二进制数中的1的个数
  4. __builtin_ffs(x); // 求x的二进制数中最低位1的位置
  5. x&-x //返回二进制最右边的1
  6. x^(1<

使用unsigned char的原因

#include
using namespace std;
int n, m, ans, E[30];
unsigned char Dp[1<<26];//防止被卡内存 
int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1, u, v; i <= m; i ++)
    {
        scanf("%d%d", &u, &v);
        E[u] ^= (1 << v);//二进制上的1代表这个点所连的边 
        E[v] ^= (1 << u);
    }
    for (int s = 1; s < (1 << n); s ++)//枚举所有子集,s这个数的二进制位上的1代表被选的某个点 
    {
        int x = s & -s, w = 31 - __builtin_clz(x);//x最右边的1,w表示x是第几位上的1
        int _s = s ^ x;//_s为删去原来s中最右边的的边的集合
        Dp[s] = max(Dp[_s], (unsigned char) (Dp[_s ^ (E[w] & _s)] + 1));
        //E[w]表示_s删掉的边(x)所连接的边的集合
        //_s ^ (E[w] & _s) 表示把_s中与E[w]共有的边删除掉后的_s,简单说就是_s中删掉与x相连的边
        ans += Dp[s];
    }
    printf("%d\n", ans);
    return 0;
}

你可能感兴趣的:(状压DP)