zoukankan      html  css  js  c++  java
  • 【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
    ", ans[i].fi, ans[i].se);
    	}
    	return 0;
    }
    
  • 相关阅读:
    动态加载JS脚本【转】
    定义并且立即执行JS匿名函数拾遗
    javascript操作ASCII码与字符对转
    win7的mklink命令
    [Yii Framework] How to get the current static page name?
    [Ubuntu] 利用Ubuntu光盘破解win7用户登录 Crark the win7 user via Ubuntu live CD
    [Ubuntu] reload the .bashrc file without logout nor restart.
    [Ubuntu] the permissions of lampp mysql and phpmyadmin
    [Zend PHP5 Cerification] Some note when studying
    [eZ publish] How to modify the $view_parameters valus in the template.
  • 原文地址:https://www.cnblogs.com/Vexoben/p/11761040.html
Copyright © 2011-2022 走看看