匈牙利算法模板 2019牛客第五场多校F
题意:给出5000个不同的值,求出元素个数最多的满足如下性质的子集:
任意两个元素的二进制表示中至少有两个bit不一样。
最初的思路:
把这些值看作节点,两个值之间如果满足二进制表示中至少两个bit不一样就连一条边,然后求这个图中的最大的完全子图(最大团问题)。但是最大团问题是NP的,因此需要找到一个性质优化之。
考虑到,如果两个值之间没有连边,那么这两个值之间只有一个bit不一样(所有值都不同),这个比之前的更简单,更可能得到良好的性质,因此考虑原图的补图,也就算说两个点之间有连线当且仅当这两个值只有一个bit不一样。
考虑一条长度为k的从值u到值v的简单路径。如果k为偶数,那么v与u有偶数个bit不一样,因此u与v之间不可能有连边。因此这个图中没有奇圈,因此这个图是二分图。于是原问题转化为求这个图的最大点独立集。
二分图的最大点独立集可以用匈牙利算法求解。
由于点独立数+匹配数 + 节点数,因此首先用匈牙利算法求出最大匹配,如果能够把所有的未匹配点都选上,然后对于每条匹配边,都选择两个点中的一个,那么这些点组成的集合就是一个最大点独立集。
先考虑左边的某个未匹配点u。由于要选u,因此所有与u相连的点都不能选。由于与u相连的点必然是已匹配点,因此这些已匹配点匹配的左边的点都必须选择(因为要二选一)。选择这些左边的点意味着与它们相连的右边的点都不能选择,而与它们相连的右边的点都一定是已匹配的点(否则就是一条增广路了),于是就这样递归处理下去。我们对这个递归访问到的点都打上标记,那么左边打上了标记的点都是要选的,右边打上了标记的点都不能选。对左边每个未匹配点都这样递归处理。
但是如果有一些匹配的点对没有与未匹配点相连怎么办?这些点对在左右两侧的点数是一样的,我们可以随便选一边的点。为了方便,这里我们选右边的点。
右边的未匹配点也可能与左边的已匹配点相连,本来也要递归处理。但是我们注意到,这样递归处理的结果是选择了与非匹配点同侧的匹配点,因此对于这个非匹配点在右侧的情况,选择的点都在右侧。
一条交错路只有三种情况:不与非匹配点相连、与左侧的非匹配点相连、与右侧的非匹配点相连。不可能同时与左右的非匹配点相连(否则就是一条增广路)。对于不与非匹配点相连的情况,根据上面的叙述,应该选右边的点(这些点都没有打上标记)。对于与左侧的非匹配点相连的情况,应该选左边的点(这些点打上了标记)。对于与右侧的非匹配点相连的情况,应该选右边的点(这些点没有打上标记)。
综上,对所有左边的非匹配点进行递归处理打标记。然后选择左边的打了标记的点和右边的没有打标记的点即可。
代码:
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define MAXN 5010
#define DEBUG 0
struct GRAPH {
vector<vector<int> > s;
void ClearEdges() {
for (auto& i : s)
i.resize(0);
}
void Init(int n) {
s.resize(n+1);
ClearEdges();
}
void AddUndi(int u, int v) {
s[u].push_back(v);
s[v].push_back(u);
}
};
int lowbit(int x) {
return x & -x;
}
bool Judge(int x, int y) {
x ^= y;
x ^= lowbit(x);
return x != 0;
}
bool left[MAXN], vis[MAXN];
int match[MAXN];
//If succeed return 1,else return 0
bool dfs_hungary(const GRAPH& g, int u) {
for (int v : g.s[u]) {
if (!vis[v]) {
vis[v] = true;
if (!match[v] || dfs_hungary(g, match[v])) {
match[v] = u;
return true;
}
}
}
return false;
}
int hungary(const GRAPH& g) {
int ans = 0, n = g.s.size();
memset(match, 0, n * sizeof(int));
for (int i = 1; i < n; ++i) {
if (left[i]) {
memset(vis, 0, n * sizeof(vis[0]));
if (dfs_hungary(g, i)) ++ans;
}
}
return ans;
}
void dfs_color(const GRAPH& g, int u, bool c) {
left[u] = c;
vis[u] = true;
for (int v : g.s[u])
if (!vis[v])
dfs_color(g, v, !c);
}
int Solve(const GRAPH& g) {
memset(vis, 0, g.s.size() * sizeof(vis[0]));
for (int i = 1; i < (int)g.s.size(); ++i)
if (!vis[i])
dfs_color(g, i, false);
return hungary(g);
}
void dfs_independent(int ans[], int& len, const GRAPH& g, int u) {
if (vis[u]) return;
vis[u] = true;
for (int v : g.s[u]) {
if (!vis[v]) {
vis[v] = true;
dfs_independent(ans, len, g, match[v]);
}
}
}
//O(n+m)
void Independent(int ans[], int& len, const GRAPH& g) {
Solve(g);
static bool paired[MAXN];
int n = g.s.size();
memset(vis, 0, n * sizeof(vis[0]));
memset(paired, 0, n * sizeof(paired[0]));
for (int i = 1; i < n; ++i) //start from 1
if (match[i])
paired[match[i]] = true;
for (int i = 1; i < n; ++i)
if (left[i] && !paired[i])
dfs_independent(ans, len, g, i);
for (int i = 1; i < n; ++i)
if ((!vis[i] && !left[i]) || (vis[i] && left[i]))
ans[len++] = i;
}
int main() {
static int a[MAXN];
GRAPH g;
int n;
scanf("%d", &n);
g.Init(n);
for (int i = 1; i <= n; ++i) {
scanf("%d", a+i);
}
for (int i = 1; i <= n; ++i) {
for (int j = i + 1; j <= n; ++j) {
if (!Judge(a[i], a[j])) {
g.AddUndi(i, j);
}
}
}
static int ans[MAXN];
int len = 0;
Independent(ans, len, g);
printf("%d\n", len);
for (int i = 0; i < len; ++i) {
printf("%d", a[ans[i]]);
if (i < len-1) {
putchar(' ');
}
}
return 0;
}