题目链接:https://ac.nowcoder.com/acm/contest/885/F
题意:给出n个数,且每个数都不同,要求找出最多的一个子集使得其中任意两个数的二进制位至少有两个不同,并输出这个集合
数据范围:1≤N≤5000, 1≤ai≤1e9
思路:首先把这n个数按照二进制位里1的个数分为奇数个1和偶数个1的集合,对于奇数个1的集合里任意两个数都是满足至少有两位二进制位不同的,偶数个1的集合里同理。那么只需要去掉奇数个1和偶数个1的集合里那些是不能的同时出现的其中一个即可。
这时候我们只需按照不同二进制位个数大于等于2的数两两连边,求一个原图的最大团(及一个最大完全图)即可,然而我们知道最大团等于补图的最大独立集,最大独立集=n-最大匹配。这是时候我们只需求按照二进制位只有一位不同的点去连边即可,而这些边正好构成了上面说的奇数集合和偶数集合的一个二分图,这时求出最大匹配,输出时把没有匹配的点输出和匹配上的点去掉一半输出。
#include <bits/stdc++.h>
using namespace std;
const int N=5e3+5;
map<int,int>mp;
int head[N],cnt,tot,n,m,cx[N],cy[N],a[N],b[N],v;
bool vis[N];
struct Edge{int to,next;}edge[N*N];
void add(int u,int v){
edge[cnt]={v,head[u]};
head[u]=cnt++;
}
bool dfs(int u){///匈牙利
vis[u]=1;
for(int i=head[u];i!=-1;i=edge[i].next){
int v=edge[i].to;
if(!vis[v]){
vis[v]=1;
if(cy[v-n]==-1||dfs(cy[v-n])){
cx[u]=v-n;
cy[v-n]=u;
return 1;
}
}
}
return 0;
}
int hungary(){///匈牙利
int res=0;
memset(vis,0,sizeof vis);
memset(cx,-1,sizeof cx);
memset(cy,-1,sizeof cy);
for(int u=1;u<=n;u++){
memset(vis,0,sizeof vis);
res+=dfs(u);
}
return res;
}
void MaxIndependentSet(){///最大独立集用匈牙利跑输出路径
memset(vis,0,sizeof vis);
///与左面的没有匹配上的点有相连性质的右面的点要标记上,最后不输出这些点
///对于没有匹配上的点,与它们相连的边比较少这样标记的点也少
for(int i=1;i<=n;i++)if(cx[i]==-1)dfs(i);
for(int i=1;i<=n;i++)if(vis[i])printf("%d ",a[i]);
for(int i=n+1;i<=n+m;i++)if(!vis[i])printf("%d ",b[i-n]);
puts("");
///左面标记的输出,右面未标记的输出
///太菜了,只会先跑匈牙利,在对没匹配上的边反跑才会求最大独立集
}
int main(){
scanf("%d",&tot);
memset(head,-1,sizeof head);cnt=0;
for(int i=1;i<=tot;i++){
scanf("%d",&v);
int num=0,tmp=v;
for(;tmp;tmp>>=1)if(tmp&1)num++;
if(num&1)a[++n]=v;///奇数个1的数
else b[++m]=v;///偶数个1的数
}
for(int i=1;i<=m;i++)mp[b[i]]=n+i;///b[i]的实际点为n+i
for(int i=1;i<=n;i++){
for(int j=0;j<32;j++){///补图
if(mp.count(a[i]^(1<<j))){
add(i,mp[a[i]^(1<<j)]);
}
}
}
printf("%d\n",tot-hungary());
MaxIndependentSet();///最大独立集
return 0;
}