网络流之最小路径覆盖问题

49 篇文章 0 订阅
41 篇文章 0 订阅

问题:给你若干个点和边,问最少用几条路可以覆盖掉全图。
问题转化:网络流
题目传送门
分析:
我们首先将原图用n条路径覆盖,每条边只经过每个节点。
现在尽量合并更多的路径(即将两个路径通过一条边首尾相连)。
可以知道,每合并两条路径,图中的路径覆盖数就会减少1。
所以我们只需要利用网络流合并相关的路径即可。

答案求解:
首先将每个节点拆成(Xi,Yi)两个节点,建立源点和汇点,分别连接(S,Xi)和(Yi,T)。
然后对于每一条原图中的边,建立边(Xi,Yi)即可。
这样每一条增广路都只会经过2个节点(Xa,Yb),对应合并的两个节点。
由于每个节点至多与一个节点合并,故边(S,Xi)和(Yi,T)容量为1。
此时的最大流对应的就是最多可以合并的路径数。
至于输出路径嘛,这题是SPJ,瞎搞就行。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <cmath> 
#define il inline
using namespace std;
const int inf=0x7fffffff;
const int maxm=1e5+1;
const int anx=150;
int head[maxm],to[maxm*2],cap[maxm*2],net[maxm*2];
int top[maxm];
int cnt=1;
il void add(int x,int y,int c){cnt++,to[cnt]=y,cap[cnt]=c,net[cnt]=head[x],head[x]=cnt;}
int flow[maxm],pre[1100],id[maxm],preflow=0,anaa;
int ans[maxm];
queue <int> dl;
bool vis[maxm];
il int BFS(int s,int t)
{
    while(!dl.empty()) dl.pop();
    memset(pre,-1,sizeof(pre));
    pre[s]=0,flow[s]=inf;
    dl.push(s);
    while(!dl.empty())
    {
        int x=dl.front();
        dl.pop();
        if(x==t) break;
        for(int i=head[x];i;i=net[i])
        if(to[i]!=s&&cap[i]>0&&pre[to[i]]==-1)
        {
            pre[to[i]]=x,id[to[i]]=i;
            flow[to[i]]=min(flow[x],cap[i]);
            dl.push(to[i]);
        }
    }
    if(pre[t]==-1) return -1;
    else return flow[t];
}
il void change_cap(int s,int t,int x)
{
    int now=t,last,sum=0;
    while(now!=s)
    {
        cap[id[now]]-=x;
        cap[id[now]^1]+=x;
        now=pre[now];
    }
}
il int maxflow(int s,int t)
{
    int max_flow=0;
    int d=0;
    while((d=(BFS(s,t)))!=-1)
    {
        change_cap(s,t,d);
        max_flow+=d;
    }
    return max_flow;
}
inline void adx(int x,int y,int cax)
{
    add(x,y,cax),add(y,x,0);
}
int main()
{
    int n,m,s=0,t;
    scanf("%d%d",&n,&m);
    t=1000;
    for(int i=1;i<=n;i++) adx(s,i,1),adx(i+anx,t,1); 
    for(int i=1;i<=m;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        adx(a,b+anx,1);
    }
    int Ans=maxflow(s,t);
    for(int i=1;i<=n;i++)
     for(int j=head[i];j;j=net[j])
      if(cap[j]==0) ans[i]=to[j]-anx;
    for(int i=1;i<=n;i++)
    {
        if(vis[i]) continue;
        int now=i;
        while(now!=-anx) 
         printf("%d ",now),vis[now]=1,now=ans[now];
        puts("");
    }
    return printf("%d\n",n-Ans)*0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值