2019牛客暑期多校训练营(第五场)F

题目链接
题意:给你一组数据,让你从中选取尽可能多的数,并且选出来的数两两之间的二进制表示不同的 位数要大于1。
思路:反向思考,如果两个数之间只有一位不同,那我们就在这两个数之间建一条边。那么就是求一个最大独立集的问题。
定理

设G是有n个节点的二分图,G的最大独立集的大小等于n减去最大匹配数。

但是此题不仅要求大小,还得把选出来的数输出来;
于是我们可以先求出这个二分图的最小点覆盖,那么剩下的就是最大独立集的点。
求二分图的最小点覆盖

1 求出二分图的最大匹配
2 从左部每个非匹配点出发,再执行一次DFS寻找增广路的过程(一定会失败),标记访问过的结点。
3 取左部未被标记的点,右部被标记的点,就得到了二分图的最小点覆盖

对于此题我们要求的是最大独立集,所以取法相反,即是去左部被标记的点和右部未被标记的点

这里我们构造二分图的时候可以根据每个数的二进制下1的个数的奇偶性分左右节点。

#include
using namespace std;
const int N = 5010,M = N*N;
int n;
int a[N];
int head[N], to[M], nextt[M], cnt = 2;
int vis[N], match[N],sign[N];
int L[N], R[N], tot1, tot2;
void add(int u, int v) {
	to[cnt] = v, nextt[cnt] = head[u], head[u] = cnt++;
}
bool solve(int x, int y) {
	bitset<32>a(x), b(y);
	int ans = 0;
	for (int i = 0; i < 32; i++)if (a[i] != b[i])ans++;
	return ans == 1;
}

bool dfs(int x) {
	vis[x] = 1;
	for (int i = head[x], y; i; i = nextt[i]) {
		if (!vis[y = to[i]]) {
			vis[y] = 1;
			if (!match[y] || dfs(match[y])) {
				match[y] = x; match[x] = y; return true;
			}
		}
	}
	return false;
}

int main() {
	scanf("%d",&n);
	for (int i = 1; i <= n; i++)scanf("%d",a+i);
	for (int i = 1; i <= n; i++) {
		bitset<32>b(a[i]);
		if (b.count() % 2)L[++tot1] = i;
		else R[++tot2] = i;
	}

	for (int i = 1; i <= tot1; i++) {
		for (int j = 1; j <= tot2; j++) {
			if (solve(a[L[i]], a[R[j]]))add(L[i], R[j]);
		}
	}

	int ans = 0;
	for (int i = 1; i <= tot1; i++) {
		memset(vis,0,sizeof(vis));
		if (dfs(L[i]))ans++;
	}
	
	memset(vis, 0, sizeof(vis));
	for (int i = 1; i <= tot1; i++) {
		if (match[L[i]])continue;
		dfs(L[i]);
	}

	printf("%d\n",n-ans);
	for (int i = 1; i <= tot1; i++) {
		if (vis[L[i]])printf("%d ",a[L[i]]);
	}
	for (int i = 1; i <= tot2; i++) {
		if (vis[R[i]]==0)printf("%d ", a[R[i]]);
	}
	return 0;
}

当然了,也可以不用匈牙利跑,也可以dinic跑网络流也是ok的,最后

最小点覆盖=最小割=最大流

最后的最大独立集就是残留网络中

1 源点能到达的左边的点
2 源点不能到达的右边的点

#include
using namespace std;
const int N = 5010,M = N*N*3;
int n;
int a[N];
int head[N], to[M], nextt[M],len[M], cnt = 2;
int vis[N], match[N],sign[N];
int cur[N];
int L[N], R[N], tot1, tot2;
int s, t;
void add(int u, int v,int c) {
	to[cnt] = v,len[cnt]=c, nextt[cnt] = head[u], head[u] = cnt++;
	to[cnt] = u,len[cnt]=0, nextt[cnt] = head[v], head[v] = cnt++;
}
bool solve(int x, int y) {
	bitset<32>a(x), b(y);
	int ans = 0;
	for (int i = 0; i < 32; i++)if (a[i] != b[i])ans++;
	return ans == 1;
}

#define inf 1<<29
int d[N],incf[N],pre[N];
bool bfs() {
	for (int i = 0; i < n + 10; i++)d[i] = 0;
	queue<int>q; 
	q.push(s); d[s] = 1;
	while (q.size()) {
		int x = q.front(); q.pop();
		for (int i = head[x]; i; i = nextt[i]) {
			int y = to[i];
			if (len[i]&&!d[y]) {
				d[y] = d[x] + 1;
				q.push(y); vis[y] = 1;
				if (y == t)return true;
			}
		}
	}
	return false;
}

int dinic(int x,int flow) {
	if (x == t)return flow;
	int rest = flow, k;
	for (int& i = cur[x]; i&&rest; i = nextt[i]) {
		int y = to[i];
		if (len[i] && d[y] == d[x] + 1) {
			k = dinic(y, min(rest, len[i]));
			if (!k)d[y] = 0;
			len[i] -= k;
			len[i ^ 1] += k;
			rest -= k;
		}
	}
	return flow - rest;
}

int main() {
	scanf("%d",&n);
	for (int i = 1; i <= n; i++)scanf("%d",a+i);
	for (int i = 1; i <= n; i++) {
		bitset<32>b(a[i]);
		if (b.count() % 2)L[++tot1] = i;
		else R[++tot2] = i;
	}

	for (int i = 1; i <= tot1; i++) {
		for (int j = 1; j <= tot2; j++) {
			if (solve(a[L[i]], a[R[j]]))add(L[i], R[j],1);
		}
	}

	s = 0, t = n + 1;
	for (int i = 1; i <= tot1; i++)add(s,L[i],1);
	for (int i = 1; i <= tot2; i++)add(R[i],t,1);

	int ans = 0;
	while (bfs()) {
		for (int i = 0; i < n + 5; i++) cur[i] = head[i];
		while (int x = dinic(s, inf))
			ans += x;
	}
		
	printf("%d\n",n-ans);
	for (int i = 1; i <= tot1; i++) {
		if (d[L[i]])printf("%d ",a[L[i]]);
	}

	for (int i = 1; i <= tot2; i++) {
		if (d[R[i]]==0)printf("%d ", a[R[i]]);
	}

	return 0;
}

你可能感兴趣的:(二分图,最大独立集)