2019牛客暑假多校训练赛第五场F题 maximum clique 1 (最大团,补图最大独立集)

题目链接: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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值