P2764 最小路径覆盖问题(网络流)

题目描述

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

输入格式:

件第1 行有2个正整数n m m n是给定有向无环图 G G 的顶点数,m G G 的边数。接下来的m行,每行有2 个正整数 i i j,表示一条有向边 (i,j) ( i , j )

输出格式:

从第1 行开始,每行输出一条路径。文件的最后一行是最少路径数。

输入样例#1:
11 12
1 2
1 3
1 4
2 5
3 6
4 7
5 8
6 9
7 10
8 11
9 11
10 11
输出样例#1:
1 4 7 10 11
2 5 8
3 6 9
3







说明

1n150,1m6000 1 ≤ n ≤ 150 , 1 ≤ m ≤ 6000

传送门!!!

解:

这是一个网络流的套路,这玩意都能建网络流,orz,蒟蒻无话可说。

对于每一个点,它最多有两条边在路径当中,所以我们建网络流,点一分为二,在图中相连的点进行连边,汇点连1,源点连1,中间连 inf i n f 跑最大流。最大流的意义就是最小路径覆盖中连边的条数。我们用 nflow n − f l o w ( 流 量 ) 就是答案。

画个图理解一下:

啦啦啦

红色就代表了选择的连边, 12345 1 − 2 − 3 − 4 , 5 , 两条路。显然有多解,所以有spj。

#include<cstdio>  
#include<iostream>  
#include<cstring>  
#include<queue>  
using namespace std;  
struct lxy{  
    int to,next,flow;  
}b[50005];  

int n,m,cnt=-1,head[505],s,t,num;  
int layer[505];  
int const inf=0x7f7f7f7f;  
int to[155];bool vis[155];  
int ans;  

void add(int op,int ed,int flow)  
{  
    b[++cnt].next=head[op];  
    b[cnt].to=ed;  
    b[cnt].flow=flow;  
    head[op]=cnt;  
}  

bool bfs()  
{  
    memset(layer,0,sizeof(layer));  
    queue <int> d;  
    layer[s]=1;d.push(s);  
    while(!d.empty())  
    {  
        int now=d.front();  
        d.pop();  
        for(int i=head[now];i!=-1;i=b[i].next)  
          if(b[i].flow!=0&&layer[b[i].to]==0)  
          {  
            layer[b[i].to]=layer[now]+1;  
            d.push(b[i].to);  
          }  
    }  
    return layer[t];  
}  

int dfs(int u,int a)  
{  
    if(a==0||u==t) return a;  
    int f,flow=0;  
    for(int i=head[u];i!=-1;i=b[i].next)  
      if(b[i].flow!=0&&layer[u]+1==layer[b[i].to])  
      {  
        f=dfs(b[i].to,min(a,b[i].flow));  
        b[i].flow-=f;  
        b[i^1].flow+=f;  
        flow+=f;  
        a-=f;  
        if(a==0) break;  
      }  
    return flow;  
}  

int dinic()  
{  
    int ans=0;  
    while(bfs())  
      ans+=dfs(s,inf);  
    return ans;  
}  

int main()  
{  
    scanf("%d%d",&n,&m);t=n*2+1;num=n;  
    for(int i=0;i<=n*2+1;i++) head[i]=-1;  
    for(int i=1;i<=m;i++)  
    {  
        int x,y;scanf("%d%d",&x,&y);  
        add(x,y+n,1);add(y+n,x,0);  
    }  
    for(int i=1;i<=n;i++) add(s,i,1),add(i,s,0);  
    for(int i=n+1;i<=n*2;i++) add(i,t,1),add(t,i,0);  
    ans=num-dinic();  
    for(int i=1;i<=n;i++)  
    {  
        for(int j=head[i];j!=-1;j=b[j].next)  
          if(b[j].flow==0)  
            to[i]=b[j].to-n;  
    }  
    for(int i=1;i<=n;i++)  
      if(vis[i]==0)  
      {  
         int p=i;  
         while(p!=-n)  
         {  
            vis[p]=1;  
            printf("%d ",p);  
            p=to[p];  
         }  
         printf("\n");  
      }  
    printf("%d",ans);  
}  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值