P2764 最小路径覆盖问题 网络流输出方案

题意:
求DAG的最小路径覆盖并输出方案。所谓最小路径覆盖是指,将原图分为若干条路径,任意两条路径不能有公共点,要使路径数量尽可能少。

最小路径覆盖,emmm好像跟最少边覆盖差不多。但这俩确实不是一个东西。

最小边覆盖:边数最小的边覆盖集称为最小边覆盖,通俗地讲,就是极小边覆盖中的最小的一个集合。
最小边覆盖在二分图中的应用:最小边覆盖 = 最大独立集 = n - 最大匹配,这个是二分图上的一个性质。
两个很小的点:必须要求原图为二分图;边覆盖集中可能有多条边覆盖同一个顶点,其实可以这条边说是为了覆盖另一个顶点而存在的。

继续说最小路径问题:
先说结论,原图中有 N N N个点,把每个点拆成两个点,记为 X i , Y i X_i,Y_i Xi,Yi,对于任意一条原图中的边 ( u , v ) (u,v) (u,v),连接 ( X u , Y v ) (X_u,Y_v) (Xu,Yv)(有向边)。很容易发现,左半部对应着每条边的起点,右半部对应着终点。然后以 X X X为左半部, Y Y Y为右半部做二分图的最大匹配,得到答案 a n s ans ans,则最小路径覆盖就是 N − a n s N-ans Nans

为什么是N-ans呢?
我们先假设有N个单独的点,那么最大匹配书即为0,那么答案就是N。
如果每匹配成功一条边,那么就意味着原图有一个点融入了另一条路径,这样的话我们就可以少开辟一条简单路径去遍历这个点(也就是最小路径-1)。匹配成功多少次,那么最小路径覆盖则会-1,所以问题就变成了二分图最大匹配问题。

再说如何把方案输出?

我们可以用网络流来跑这个二分图,我们知道跑完网络流的图是会被改变的容量不变,流量会变。假设我们发现某条边的容量==流量并且容量不为0,则代表这条边有流量经过,那么我们则可以把这个u,v这两个点做一下标记。用两个数组pre,lst ,pre代表这个点之前是有哪个点流过来的,lst代表这个点之后会流向哪个点。

最后遍历N个点,如果遇到某点的pre==0(意味着这个点是某条简单路径的起点),那么就顺着这个点输出这个简单路径即可。

代码:

#include<bits/stdc++.h>

using namespace std;

const int maxn = 1010;
const int maxm = 1e6+10;

struct edge{
    long long to, next, cap, flow;  // 容量 和 流量
}e[maxm << 1]; int head[maxn], tot;
int n, m, s, t, d[maxn], cur[maxn]; // d[]分层,cur[]弧优化

inline void addedge(int u, int v, int w) {
    e[++tot] = {v, head[u], w, 0}, head[u] = tot;
    e[++tot] = {u, head[v], 0, 0}, head[v] = tot;
}
inline bool bfs() { // 分层 t-s路径优化
    memset(d, 0x3f, sizeof d);
    queue<int> q; q.push(t);
    d[t] = 0;
    while(!q.empty()) {
        int x = q.front(); q.pop();
        for(int i = head[x]; ~i; i = e[i].next) {
            int j = e[i].to;
            if (d[j] == d[0] && e[i^1].cap > e[i^1].flow) {
                d[j] = d[x] + 1;
                if (j == s) return true;
                q.push(j);
            }
        }
    }
    return false;
}
long long dfs(int x, long long flow, long long used = 0) {
//used多路增广
    if (x == t) return flow;
    //for(int i = cur[x]; ~i && flow != used; i = e[ cur[x] = i ].next) {  // 当前弧优化
    for(int i = head[x]; ~i && flow != used; i = e[i].next) {
        long long j = e[i].to, f;
        if (d[x] - 1 == d[j] && e[i].cap > e[i].flow) {
            f = dfs(j, min(e[i].cap-e[i].flow, flow-used));
            if (f) e[i].flow += f, e[i^1].flow -= f, used += f;
        }
    }
    if (!used) d[x] = 0;   // 剪枝优化
    return used;
}
int dinic(){
    long long maxflow = 0;
    while(bfs()) {   // 每次dfs分层都能找到多条增广路
        //memcpy(cur, head, sizeof head);
        maxflow += dfs(s, INT_MAX);
    }
    return n-maxflow;
}
void init(){
    memset(head,-1,sizeof head);
    tot=-1;
}
int pre[maxn],lst[maxn];
int main() {
    //freopen("1.in","r",stdin);
    init();
    cin>>n>>m;
    for(int i=0;i<m;i++){
        int x,y;
        cin>>x>>y;
        addedge(x,y+n,1);
    }
    s=0,t=2*n+2;
    for(int i=1;i<=n;i++){
        addedge(s,i,1);
        addedge(i+n,t,1);
    }

    int ans=dinic();
    for(int i=1;i<=n;i++){
        for(int j=head[i];~j;j=e[j].next){
            int v=e[j].to;
            if(e[j].cap-e[j].flow==0&&e[j].flow==1){
                pre[v-n]=i;
                lst[i]=v-n;
            }
        }
    }
    for(int i=1;i<=n;i++){
        if(!pre[i]){
            int u=i;
            while(lst[u]!=0){
                cout<<u<<" ";
                u=lst[u];
            }
            cout<<u<<endl;
        }
    }
    cout<<ans<<endl;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值