【题解】最小路径覆盖方案(最大流求二分图最大匹配)

版权声明:Powered By Fighter https://blog.csdn.net/qq_30115697/article/details/88749001

题意

原题传送门

思路

在解决这道题之前,我们先讲一下用最大流求解二分图最大匹配的做法。如果巨佬您已经熟练掌握了,那完全可以跳过。不过你都这么巨了,怎么会来看我的blog呢

我们一般是把每个点拆成两个,分为出点和入点,顾名思义出点连的边都是从他出发,连向其他点;入点连的边都是从其它点出发,连到当前点。然后我们再建立一个超级源点和一个超级汇点,从源点向所有点的出点连边,从所有入点向汇点连边,然后在图中按照入点和出点的定义互相之间连边(以上所有的边权值均为1)。然后直接在图上跑最大流即可。

那么为什么按照这样的连边方式就可以完成匹配呢,来看图解~~

graph.png

如图,图中每条边权为1。那么我们来回顾最大流找增广路的过程,我们会找到一条 s s -> t t 的路径。而在这个图中,每条路径上除了 s s t t 只有两个点,而且每条路径流量均为一。那么我们每找到一条增广路,就相当于找到了一对匹配,而由于求最大流的过程本身就是带撤销的,所以最后的最大流就是二分图的最大匹配。


然后回到这道题。我们在一开始的时候把每一个点看成一条路径,那么显然我们要尽可能地把这些点合并成数量最少的链。那么我们可以想到,在一条链上,每个点只有一个入度和一个出度,那么我们类比到二分图,发现两个点能合并到一起,就相当于一个匹配,所以我们对整个图,把每个点拆开,跑一边最大流,即做一遍二分图匹配,就能求出最多的能够合并的点数。那么显然剩下的不能被合并的点就是链的数量。

至于记录方案,我们可以在求最大流 d f s dfs 时记录下一个到达的点(虽然代码里数组名叫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;
}

猜你喜欢

转载自blog.csdn.net/qq_30115697/article/details/88749001