【题解】最小路径覆盖方案(最大流求二分图最大匹配)
题意
思路
在解决这道题之前,我们先讲一下用最大流求解二分图最大匹配的做法。如果巨佬您已经熟练掌握了,那完全可以跳过。不过你都这么巨了,怎么会来看我的blog呢
我们一般是把每个点拆成两个,分为出点和入点,顾名思义出点连的边都是从他出发,连向其他点;入点连的边都是从其它点出发,连到当前点。然后我们再建立一个超级源点和一个超级汇点,从源点向所有点的出点连边,从所有入点向汇点连边,然后在图中按照入点和出点的定义互相之间连边(以上所有的边权值均为1)。然后直接在图上跑最大流即可。
那么为什么按照这样的连边方式就可以完成匹配呢,来看图解~~
如图,图中每条边权为1。那么我们来回顾最大流找增广路的过程,我们会找到一条->的路径。而在这个图中,每条路径上除了和只有两个点,而且每条路径流量均为一。那么我们每找到一条增广路,就相当于找到了一对匹配,而由于求最大流的过程本身就是带撤销的,所以最后的最大流就是二分图的最大匹配。
然后回到这道题。我们在一开始的时候把每一个点看成一条路径,那么显然我们要尽可能地把这些点合并成数量最少的链。那么我们可以想到,在一条链上,每个点只有一个入度和一个出度,那么我们类比到二分图,发现两个点能合并到一起,就相当于一个匹配,所以我们对整个图,把每个点拆开,跑一边最大流,即做一遍二分图匹配,就能求出最多的能够合并的点数。那么显然剩下的不能被合并的点就是链的数量。
至于记录方案,我们可以在求最大流时记录下一个到达的点(虽然代码里数组名叫pre,但实际上应该是下一个),最后顺着pre数组输出一条链即可。
代码
#include <bits/stdc++.h>
#define MAXN 5005
#define MAXM 100005
#define INF 0x3f3f3f3f
using namespace std;
int n, m, cnt = 1;
int head[MAXN], vet[MAXM], Next[MAXM], cost[MAXM];
void add(int x, int y, int w){
cnt++;
Next[cnt] = head[x];
head[x] = cnt;
vet[cnt] = y;
cost[cnt] = w;
}
int vis[MAXN], cur[MAXN], dep[MAXN];
bool bfs(){
for(int i = 0; i <= n+n+1; i++){
vis[i] = 0;
cur[i] = head[i];
dep[i] = INF;
}
queue<int> q;
q.push(0);
vis[0] = 1;
dep[0] = 1;
while(!q.empty()){
int x = q.front();
q.pop();
vis[x] = 0;
for(int i = head[x]; i; i = Next[i]){
int v = vet[i];
if(cost[i] && dep[v] > dep[x]+1){
dep[v] = dep[x] + 1;
if(!vis[v]){
vis[v] = 1;
q.push(v);
}
}
}
}
return dep[n+n+1] != INF;
}
int ans, pre[MAXN], tag[MAXN];
int dfs(int x, int flow){
if(x == n+n+1){
ans += flow;
return flow;
}
int used = 0;
for(int &i = cur[x]; i; i = Next[i]){
int v = vet[i];
if(cost[i] && dep[v] == dep[x]+1){
int m = dfs(v, min(flow-used, cost[i]));
if(m){
pre[x] = v;
cost[i] -= m;
cost[i^1] += m;
used += m;
if(used == flow)break;
}
}
}
return used;
}
void dinic(){
while(bfs()){
dfs(0, INF);
}
for(int i = 1; i <= n; i++){
if(!pre[i]) continue;
int now = i;
while(now != 0){
now = (now-1)%n+1;
printf("%d ", now);
int t = pre[now];
pre[now] = 0;
now = t;
}
puts("");
}
cout << n-ans << endl;
}
int main()
{
cin >> n >> m;
int x, y;
for(int i = 1; i <= m; i++){
scanf("%d%d", &x, &y);
add(x, y+n, 1);
add(y+n, x, 0);
}
for(int i = 1; i <= n; i++){
add(0, i, 1);
add(i, 0, 0);
add(i+n, n+n+1, 1);
add(n+n+1, i+n, 0);
}
dinic();
return 0;
}