题目链接
题意:给一个无权图(可以有环),求每个子图最大独立集大小的和。
思路:设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 大小的数据。
- __builtin_ctz(x); // 求x的二进制数末尾0的个数
- __builtin_clz(x); // 求x的二进制数前导0的个数
- __builtin_popcount(x); // 求x的二进制数中的1的个数
- __builtin_ffs(x); // 求x的二进制数中最低位1的位置
- x&-x //返回二进制最右边的1
- x^(1<<k)//把二进制位上的第k+1位的1变为0.
#include<bits/stdc++.h>
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;
}