搜索+思维好题
我们考虑在什么情况先x&y=0,因为&相当于乘法也就是说两个1相见的时候才是1,那么我们可以得到,x,y二进制表达之后,x有1的位置,y必定都是0,x是0的位置y可以是0也可以是1,比如x=10011(假设x的位数已经是最大),那么y最大就是01100,也可以将y上任意一个1换成0,也就是说可以将y拆成{00100,01000,00000}所以我们可以得出,对于一个还没有加入任何联通块的数字,将他按位取反后,在将1任意取出,都是可以和当前数字链接的,取反如何做呢,我们发现最大上限为(1<<n)-1,所以x取反+x=(1<<n)-1,所以用这个数-x就是x取反,然后对于取反,我们枚举取出每一位,然后用更短的二进制去更新连通性
代码
//By AcerMo
#include<cmath>
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int M=(1<<22)+50;
bool vis[M],mark[M];
int ans=0,n,m,a[M];
int read()
{
int x=0;char ch=getchar();
while (ch>'9'||ch<'0') ch=getchar();
while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x;
}
int main()
{
queue<int>q;
n=read();m=read();
for (int i=1;i<=m;i++)
a[i]=read(),mark[a[i]]=1;//标记这个数存在
for (int i=0;i<(1<<n);i++)
{
if ((!mark[i])||vis[i]) continue;//判断存在或已被使用
ans++;vis[i]=1;//没用过就是新联通块
int emm=(1<<n)-1-i;//却反
if (!vis[emm]) q.push(emm);//看取反是不是被用过,被用过那么根据对称性,当前数一定被搜过
vis[emm]=1;
while (q.size())
{
int u=q.front();q.pop();
for (int k=0;k<n;k++)
{
if (u&(1<<k)) //这位存在1
{
int qlm=u^(1<<k);//将这一位抹去
if (!vis[qlm])//判断是否搜到过
{
vis[qlm]=1;q.push(qlm);//继续搜索
if (mark[qlm])//存在与原数列
{
int gll=(1<<n)-1-qlm;//取反去更新
if (!vis[gll]) vis[gll]=1,q.push(gll);
}
}
}
}
}
}
cout<<ans;
return 0;
}