P2764 最小路径覆盖问题

题意

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

思路

有一个定理:最小路径覆盖数=|G|-二分图最大匹配数(|G|是有向图中的总边数)
所以若当前图若没有边的话,就是每个节点都算一条路径,就是 V V V条路径,每连一条边,都少一条路径,这个定理是成立的,所以要尽量多连边,才能使最小路径覆盖数最小,若当前图存在一些边的话,要求最小覆盖数,把这个图当做二分图求最大匹配数就好了。
对于本题:拆点,把每个点拆成一个入点和一个出点,再把给出的边 u − v u-v uv连上,就是用u的出点连v的入点,再建立超级源点,汇点,讲所有的入点与超级源点连起来,把所有出点与超级汇点连起来,构成一个二分图,跑最大流,要求输出每一条路径,可以顺便记录下路径。
洛谷上偷张图:
在这里插入图片描述

#include<bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
const int N = 1e5 + 10;
int n, m, s, t, h[N], cnt, dep[N], cur[N], to[N], tag[N];
struct node {
    int v, w, nt;
} no[N];
void add(int u, int v, int w) {
    no[cnt] = node{v, w, h[u]};
    h[u] = cnt++;
}
int bfs() {
    queue<int> q;
    memset(dep, 0, sizeof dep);
    dep[s] = 1;
    q.push(s);
    while(!q.empty()) {
        int u = q.front();
        q.pop();
        for(int i = h[u]; ~i; i = no[i].nt) {
            int v = no[i].v;
            if(!dep[v] && no[i].w > 0) {
                dep[v] = dep[u] + 1;
                q.push(v);
            }
        }
    }
    return dep[t] > 0;
}
int dfs(int u, int flow) {
    if(u == t)
        return flow;
    for(int i = cur[u]; ~i; i = no[i].nt) {
        int v = no[i].v;
        if(dep[v] == dep[u] + 1 && no[i].w > 0) {
            int res = dfs(v, min(flow, no[i].w));
            if(res > 0) {
                no[i].w -= res;
                no[i ^ 1].w += res;
                if(u != s && v != t)
                    to[u] = v;//记录路径
                if(u != s)//记录本节点非路径的根节点
                    tag[v - n] = 1;
                return res;
            }
        }
    }
    return 0;
}
int dinic() {
    int res = 0;
    while(bfs()) {
        for(int i = 0; i <= cnt; i++)
            cur[i] = h[i];
        while(int d = dfs(s, INF))
            res += d;
    }
    for(int i = 1; i <= n; i++) {
        if(!tag[i]) {
            int u = i;
            printf("%d ", u);
            while(to[u] && to[u] != t) {
                printf("%d ", to[u] - n);
                u = to[u] - n;
            }
            puts("");
        }
    }
    return res;
}
int main() {
    //freopen("1.in", "r", stdin);
    memset(h, -1, sizeof h);
    scanf("%d%d", &n, &m);
    t = 2 * n + 1;
    for(int i = 1; i <= n; i++) {
        add(s, i, 1), add(i, s, 0);
        add(i + n, t, 1), add(t, i + n, 0);
    }
    for(int u, v, i = 1; i <= m; i++) {
        scanf("%d%d", &u, &v);
        add(u, v + n, 1), add(n + v, u, 0);
    }
    printf("%d\n", n - dinic());
    return 0;
}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值