【HAOI2017】新型城市化,二分图最大匹配的必经与可行点边
题意
给定一张图的补图,保证原图可以被划分为不超过两个团。对于补图中的每条边,判断在原图中加入这条边后,最大团大小是否会增大。
数据范围:
题解
容易发现,补图是一张二分图。
因此,原图中的最大团就对应补图中的最大独立集。
二分图最大独立集 点数 最大匹配数
只需判断每条边是否一定在最大匹配上。
先用最大流跑一遍二分图匹配,再在残量网络上跑一遍 tarjan 缩点。如果一条边被匹配,而且两个端点不在同一个强连通分量中,那么这条边是必须边。
两个端点不在一个强连通分量中,就是说没有一条从一个端点到另一个端点的增广路,也就是说这条边是无可替代的。
一些拓展
二分图最大匹配的可行边
指存在一个最大匹配包含这条边。
要么这条边已经在求出的匹配中,要么这条边两个端点在同一个强连通分量里。
二分图最大匹配的必经点
只需求出非必经点即可。
从 开始,走 的边,走到所有 部的点都是答案;从 开始,走 的边,走到所有 部的点都是答案。
感性理解:首先一个不在匹配的点一定会被走到,这些点都是非必经点。一个最大匹配中的点如果是非必经点,一定可以被一个不在匹配中的点替代掉。从某个不在匹配中的 部点出发走非匹配边到 部,如果能走匹配边回来,那么翻转所有匹配边和非匹配边,就能用不在匹配中的那个点代替掉匹配中的 部点。
二分图最大匹配的可行点
存在一条可行边或者必经边就是可行点。
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话