[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;
}

猜你喜欢

转载自www.cnblogs.com/Vexoben/p/11761040.html