匈牙利算法模板 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;
}
posted @ 2024-09-28 13:41  寻找繁星  阅读(1)  评论(0编辑  收藏  举报