最小路径覆盖(模板)

对于一个给定的有向无环图(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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值