对于一个给定的有向无环图(DAG),求其最小路径覆盖。题目可参见洛谷P2764。
【问题分析】
有向无环图最小路径覆盖,可以转化成二分图最大匹配问题,从而用最大流解决。
【建模方法】
构造二分图,把原图每个顶点i拆分成二分图X,Y集合中的两个顶点Xi和Yi。对于原图中存在的每条边(i,j),在二分图中连接边(Xi,Yj)。然后把二分图最大匹配模型转化为网络流模型,求网络最大流。
最小路径覆盖的条数,就是原图顶点数,减去二分图最大匹配数。沿着匹配边查找,就是一个路径上的点,输出所有路径即可。
【建模分析】
对于一个路径覆盖,有如下性质:
1、每个顶点属于且只属于一个路径。 2、路径上除终点外,从每个顶点出发只有一条边指向路径上的另一顶点。所以我们可以把每个顶点理解成两个顶点,一个是出发,一个是目标,建立二分图模型。该二分图的任何一个匹配方案,都对应了一个路径覆盖方案。如果匹配数为0,那么显然路径数=顶点数。每增加一条匹配边,那么路径覆盖数就减少一个,所以路径数=顶点数 - 匹配数。要想使路径数最少,则应最大化匹配数,所以要求二分图的最大匹配。注意,此建模方法求最小路径覆盖仅适用于有向无环图,如果有环或是无向图,那么有可能求出的一些环覆盖,而不是路径覆盖。
洛谷题目参考代码:
// luogu-judger-enable-o2
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
struct edge {
int to, cap, rev;
};
vector<edge>G[500];
int level[500], iter[500];
int n, m;
void addedge(int from, int to, int cap)
{
edge e;
e.to = to; e.cap = cap; e.rev = G[to].size();
G[from].push_back(e);
e.to = from; e.cap = 0; e.rev = G[from].size() - 1;//一定要小心反向边的cap为0!!!
G[to].push_back(e);
}
void bfs(int s)
{
memset(level, -1, sizeof(level));//每次bfs的时候构图都不同,要memset
queue<int>que;
level[s] = 0;
que.push(s);
while (!que.empty()) {
int t = que.front(); que.pop();
for (int i = 0; i < G[t].size(); i++) {
edge e = G[t][i];
if (e.cap&&level[e.to] < 0) {
level[e.to] = level[t] + 1;
que.push(e.to);
}
}
}
}
int dfs(int v, int t, int f)
{
if (v == t)
return f;
for (int &i = iter[v]; i < G[v].size(); i++) {//因为每次dfs的时候如果找到解就return了,所以有必要记录上次这个点搜到哪了
edge &e = G[v][i];
if (e.cap&&level[e.to] > level[v]) {
int d = dfs(e.to, t, min(f, e.cap));
if (d) {
e.cap -= d;
G[e.to][e.rev].cap += d;
return d;
}
}
}
return 0;
}
int maxflow(int s, int t)
{
int flow = 0;
for (;;) {
bfs(s);
if (level[t] < 0)//说明此时已经不存在没有搜过的路了
return flow;
memset(iter, 0, sizeof(iter));
int f;
while (f = dfs(s, t, 1 << 30))//要搜完当期状况下的所有可能
flow += f;
}
}
int main()
{
int i, j;
cin >> n >> m ;
for (i = 1; i <= m; i++) {
int a, b;
scanf("%d %d", &a, &b);
addedge(a, b+150, 1);
}
for (i = 1; i <= n; i++) {
addedge(0, i, 1);
addedge(i+150, 400, 1);
}
int ans = n - maxflow(0, 400);
bool vis[500]; memset(vis, 0, sizeof(vis));
for (i = 1; i <= n; i++) {
if (!vis[i]) {
cout << i;
int k = i;
for (;;) {
bool find = false;
for (j = 0; j < G[k].size(); j++) {
if (G[k][j].cap == 0&&G[k][j].to>150) {
cout << " " << G[k][j].to-150;
k = G[k][j].to - 150; vis[k] = true; find = true;
break;
}
}
if (!find)break;
}
cout << endl;
}
}
cout << ans << endl;
return 0;
}