【HAOI2017】新型城市化,二分图最大匹配的必经与可行点边

题意

给定一张图的补图,保证原图可以被划分为不超过两个团。对于补图中的每条边,判断在原图中加入这条边后,最大团大小是否会增大。

数据范围: \(n \leq 100000, m \leq 150000\)

题解

容易发现,补图是一张二分图。

因此,原图中的最大团就对应补图中的最大独立集。

二分图最大独立集 \(=\) 点数 \(-\) 最大匹配数

只需判断每条边是否一定在最大匹配上。

先用最大流跑一遍二分图匹配,再在残量网络上跑一遍 tarjan 缩点。如果一条边被匹配,而且两个端点不在同一个强连通分量中,那么这条边是必须边。

两个端点不在一个强连通分量中,就是说没有一条从一个端点到另一个端点的增广路,也就是说这条边是无可替代的。

一些拓展

二分图最大匹配的可行边

指存在一个最大匹配包含这条边。

要么这条边已经在求出的匹配中,要么这条边两个端点在同一个强连通分量里。

二分图最大匹配的必经点

只需求出非必经点即可。

\(S\) 开始,走 \(cap=1\) 的边,走到所有 \(X\) 部的点都是答案;从 \(T\) 开始,走 \(cap=0\) 的边,走到所有 \(Y\) 部的点都是答案。

感性理解:首先一个不在匹配的点一定会被走到,这些点都是非必经点。一个最大匹配中的点如果是非必经点,一定可以被一个不在匹配中的点替代掉。从某个不在匹配中的 \(X\) 部点出发走非匹配边到 \(Y\) 部,如果能走匹配边回来,那么翻转所有匹配边和非匹配边,就能用不在匹配中的那个点代替掉匹配中的 \(X\) 部点。

二分图最大匹配的可行点

存在一条可行边或者必经边就是可行点。

#pragma GCC optimize("2,Ofast,inline")
#include<bits/stdc++.h>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define LL long long
#define pii pair<int, int>
using namespace std;
const int N = 1e6 + 10;

template <typename T> T read(T &x) {
	int f = 0;
	register char c = getchar();
	while (c > '9' || c < '0') f |= (c == '-'), c = getchar();
	for (x = 0; c >= '0' && c <= '9'; c = getchar())
		x = (x << 3) + (x << 1) + (c ^ 48);
	if (f) x = -x;
	return x;
}

int n, m, E, S, T, tim, sccn;
int u[N], v[N];
int fir[N], nex[N], arr[N], vis[N], cap[N], col[N];
int dis[N], cur[N];
int top, st[N], dfn[N], low[N], scc[N];
vector<pii> ans;

inline void Add_Edge(int x, int y, int c) {
	nex[++E] = fir[x];
	fir[x] = E; arr[E] = y; cap[E] = c;
	nex[++E] = fir[y];
	fir[y] = E; arr[E] = x; cap[E] = 0;
}

void dfs(int x) {
	vis[x] = 1;
	for (int i = fir[x]; i; i = nex[i]) {
		if (vis[arr[i]]) continue;
		col[arr[i]] = col[x] ^ 1;
		dfs(arr[i]);
	}
}

int bfs() {
	queue<int> Q;
	memset(dis, 0x3f, sizeof dis);
	dis[S] = 0; Q.push(S);
	while (!Q.empty()) {
		int x = Q.front(); Q.pop();
		for (int i = fir[x]; i; i = nex[i]) {
			if (cap[i] && dis[arr[i]] > dis[x] + 1) {
				dis[arr[i]] = dis[x] + 1;
				Q.push(arr[i]);
			}
		}
	}
	return (dis[T] < 1e9);
}

int dinic(int x, int mf) {
	if (!mf || x == T) return mf;
	int ans = 0;
	for (int &i = cur[x]; i; i = nex[i]) {
		if (!cap[i] || dis[arr[i]] != dis[x] + 1) continue;
		int del = dinic(arr[i], min(cap[i], mf));
		cap[i] -= del; cap[i ^ 1] += del;
		ans += del; mf -= del;
		if (!mf) break;
	}
	return ans;
}

void tarjan(int x) {
	dfn[x] = low[x] = ++tim;
	st[++top] = x;
	for (int i = fir[x]; i; i = nex[i]) {
		if (!cap[i]) continue;
		if (!dfn[arr[i]]) {
			tarjan(arr[i]);
			low[x] = min(low[x], low[arr[i]]);
		}
		else if (!scc[arr[i]]) {
			low[x] = min(low[x], dfn[arr[i]]);
		}
	}
	if (low[x] == dfn[x]) {
		int y;
		++sccn;
		do {
			y = st[top--];
			scc[y] = sccn;
		} while (x != y);
	}
}

int main() {
	read(n); read(m);
	for (int i = 1; i <= m; ++i) {
		int x, y;
		read(x); read(y);
		Add_Edge(x, y, 0);
		u[i] = x; v[i] = y;
	}
	for (int i = 1; i <= n; ++i) {
		if (!vis[i]) {
			dfs(i);
		}
	}
	memset(fir, 0, sizeof fir);
	E = 1;
	for (int i = 1; i <= m; ++i) {
		if (col[u[i]]) swap(u[i], v[i]);
		Add_Edge(u[i], v[i], 1);
	}
	S = n + 1; T = n + 2;
	for (int i = 1; i <= n; ++i) {
		if (!col[i]) Add_Edge(S, i, 1);
		else Add_Edge(i, T, 1);
	}
	while (bfs()) {
		for (int i = 1; i <= n + 2; ++i) {
			cur[i] = fir[i];
		}
		dinic(S, n);
	}
	for (int i = 1; i <= n + 2; ++i) {
		if (!scc[i]) tarjan(i);
	}
	for (int i = 1; i <= n; ++i) {
		if (col[i]) continue;
		for (int j = fir[i]; j; j = nex[j]) {
			if (cap[j]) continue;
			if (scc[i] != scc[arr[j]] && arr[j] != S && arr[j] != T) {
				ans.pb(mp(i, arr[j]));
			}
		}
	}
	for (int i = 0; i < ans.size(); ++i) {
		if (ans[i].fi > ans[i].se) {
			swap(ans[i].fi, ans[i].se);
		}
	}
	sort(ans.begin(), ans.end());
	cout << ans.size() << endl;
	for (int i = 0; i < ans.size(); ++i) {
		printf("%d %d\n", ans[i].fi, ans[i].se);
	}
	return 0;
}
posted @ 2019-10-29 19:39  Vexoben  阅读(569)  评论(0编辑  收藏  举报