洛谷 P2764——最小路径覆盖问题【二分图最大匹配 & 最大流 & 并查集记录路径】

题目传送门


题目描述

给定有向图 G = ( V , E ) G=(V,E) 。设 P P G G 的一个简单路(顶点不相交)的集合。如果 V V 中每个定点恰好在 P P 的一条路上,则称 P P G G 的一个路径覆盖。 P P 中路径可以从 V V 的任何一个定点开始,长度也是任意的,特别地,可以为 0 0 G G 的最小路径覆盖是 G G 所含路径条数最少的路径覆盖。设计一个有效算法求一个 GAP (有向无环图) G G 的最小路径覆盖。

提示:设 V = { 1 , 2 , . . . , n } V=\{1,2,...,n\} ,构造网络 G 1 = { V 1 , E 1 } G_1=\{V_1,E_1\} 如下:

V 1 = { x 0 , x 1 , . . . , x n } { y 0 , y 1 , . . . , y n } V_1=\{x_0,x_1,...,x_n\}\cup\{y_0,y_1,...,y_n\}

E 1 = { ( x 0 , x i ) : i V } { ( y i , y 0 ) : i V } { ( x i , y j ) : ( i , j ) E } E_1=\{(x_0,x_i):i\in V\}\cup\{(y_i,y_0):i\in V\}\cup\{(x_i,y_j):(i,j)\in E\}

每条边的容量均为 1 1 ,求网络 G 1 G_1 ( x 0 , y 0 ) (x_0,y_0) 最大流。


输入格式

第一行有 2 2 个正整数 n n m m n n 是给定 GAP \text{GAP} (有向无环图) G G 的顶点数, m m G G 的边数。接下来的 m m 行,每行有两个正整数 ii 和 j j 表示一条有向边 ( i , j ) (i,j)


输出格式

从第1 行开始,每行输出一条路径。文件的最后一行是最少路径数。


输入

11 12
1 2
1 3
1 4
2 5
3 6
4 7
5 8
6 9
7 10
8 11
9 11
10 11


输出

1 4 7 10 11
2 5 8
3 6 9
3


说明/提示

1 n 150 , 1 m 6000 1\leq n\leq 150,1\leq m\leq 6000
由@FlierKing提供SPJ


题解

  • 一道二分图的题,在网络流上体现为:
    最小路径覆盖 = |G| - 最大匹配数 -> 最小路径覆盖=|G|-最大流

  • 证明如下:

    首先,若最大匹配数为0,则二分图中无边,也就是说有向图G中不存在边,那么

    显然:最小路径覆盖=|G|-最大匹配数=|G|-0=|G|。

    若此时增加一条匹配边x1–y2,则在有向图|G|中,x、y在同一条路径上,最小路径覆盖数减少一个。

    继续增加匹配边,每增加一条,最小路径覆盖数减少一个,则公式:最小路径覆盖=|G|-最大匹配数得证。

  • 建模方式:
    在这里插入图片描述

  • 最后的方案可以利用残量网络用并查集维护。

    即从1到n枚举,从每个点向外扫一圈,如果有流从这条边经过,并流向y+n,则合并x与y。

    然后n^2输出方案即可。


AC-Code

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);

const int inf = 0x3f3f3f3f;
const int maxn = 6000 * 2 + 150 * 2;
int n, m;

struct Edge {
	int to;
	int next;
	int val;
}edge[maxn << 1]; // 双向边,开 2 倍数组
int head[maxn];
int cnt; // 边的数量,从 0 开始编号
int depth[maxn]; // 分层图标记深度
int cur[maxn]; // 当前弧优化,记录当前点 u 循环到了哪一条边
void add(int u, int v, int w) {
	edge[cnt].to = v;
	edge[cnt].next = head[u];
	edge[cnt].val = w;
	head[u] = cnt++;
}

// bfs分层图
bool bfs(int s, int t) {
	queue<int>q;
	memset(depth, 0, sizeof depth);
	depth[s] = 1; // 源点深度为 1
	cur[s] = head[s];
	q.push(s);
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		for (int i = head[u]; ~i; i = edge[i].next) {
			int v = edge[i].to;
			if (edge[i].val > 0 && depth[v] == 0) { // 如果残量不为0,且 v 还未分配深度
				depth[v] = depth[u] + 1;
				cur[v] = head[v]; //------当前弧优化,注意在bfs里,这样做到了“按需赋值”,因为Dinic本来上界就松得一匹, BFS的过程中不连通的点根本就不用再管了...
				if (v == t) // -----分层图汇点优化:遇到汇点直接返回------
					return true;
				q.push(v);
			}
		}
	}
	return depth[t]; // 汇点深度为 0:不存在分层图,返回false;
					 //           非 0 :存在增广路,返回true
}

// dfs寻找增广路
int dfs(int u, int flow, int t) {
	if (u == t || flow <= 0) // 到达汇点
		return flow;
	int rest = flow;
	for (int i = cur[u]; ~i; i = edge[i].next) {
		int v = edge[i].to;
		if (depth[v] == depth[u] + 1 && edge[i].val != 0) { // 满足分层图、残量>0 两个条件
			int k = dfs(v, min(rest, edge[i].val), t); // 向下增广
			if (k < 0) // ------无用点优化-----
				depth[v] = 0;
			rest -= k;
			edge[i].val -= k; // 正向边减
			edge[i ^ 1].val += k; // 反向边加
			if (rest <= 0) //------剩余量优化:在进行增广的时候,如果该节点已经没有流量,直接退出------
				break;
		}
	}
	return flow - rest; // flow:推送量,rest:淤积量,flow - rest:接受量/成功传递量
}
int Dinic(int s, int t) {
	int ans = 0;
	while (bfs(s, t)) {
		ans += dfs(s, inf, t);
	}
	return ans;
}

int f[maxn];
int findx(int x) {
	return f[x] == x ? x : f[x] = findx(f[x]);
}
void join(int a, int b) {
	int fa = findx(a);
	int fb = findx(b);
	if (fa != fb)	f[fb] = fa;
}
int main() {
	ios;
	while (cin >> n >> m) {
		::cnt = 0;
		memset(head, -1, sizeof head);
		int s_ = 0;
		int t_ = n * 2 + 1;
		for (int i = 1; i <= n; i++) {
			add(s_, i, 1);
			add(i, s_, 0);
		}
		for (int i = 1 + n; i <= 2 * n; i++) {
			add(i, t_, 1);
			add(t_, i, 0);
		}
		for (int i = 1, a, b; i <= m; i++) {
			cin >> a >> b;
			add(a, b + n, 1);
			add(b + n, a, 0);
		}
		int ans = Dinic(s_, t_);
		for (int i = 1; i <= n; ++i)	f[i] = i;
		for (int i = 1; i <= n; ++i) {
			for (int u = head[i]; ~u; u = edge[u].next) {
				if (edge[u].to == s_ || edge[u].to == t_)	continue;
				if (!edge[u].val)
					join(i, edge[u].to - n);
			}
		}
		for (int i = n; i; --i) {
			bool flag = false;
			for (int j = 1; j <= n; ++j) {
				if (findx(j) == i) {
					cout << j << " ";
					flag = true;
				}
			}
			if (flag)	cout << endl;
		}
		cout << n - ans << endl;
	}
}
发布了104 篇原创文章 · 获赞 60 · 访问量 5844

猜你喜欢

转载自blog.csdn.net/Q_1849805767/article/details/103652484