网络流24题-最小路径覆盖 (最小路径覆盖)

题目:

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

设计一个有效算法求一个有向无环图G的最小路径覆盖。

输入格式:

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

输出格式:

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

链接: https://loj.ac/problem/6002

思路:

我们将每个点拆成ii+n,因为顶点不相交,那么这个图就成了一个二分图

最小路径覆盖=顶点数-二分图最大匹配

证明:我们先把每个顶点看成一条单独的路径,花费记为1,假设一开始有11个点,那么总花费是11,此时如果有一条边连接了1和2,即1和2匹配,那么总花费变成10

所以要想用最小的路径覆盖所有顶点,那么要求出这个二分图中的最大匹配,用顶点总数减去二分图最大匹配即为答案。跑一遍最大流来求二分图的最大匹配。

然后输出路径的方法是:如果这条边上的流量为0,那么说明经过了这条路径,再以这个点为起点继续寻找,跑一遍dfs即可

代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
using namespace std;
const int MAXN=2e5+5;
const int INF=0x7fffffff;
typedef long long ll;
int head[MAXN],tot,visit[MAXN];
int s,t,n,m;
struct node
{
    int to,nxt,flow;
}e[MAXN];
void add(int x,int y,int z)
{
    e[tot].to=y;e[tot].nxt=head[x];e[tot].flow=z;head[x]=tot++;
}
void add_edge(int x,int y,int z)
{
    add(x,y,z);add(y,x,0);
}
int deep[MAXN],l,r,q[MAXN];
bool bfs()
{
    memset(deep,0,sizeof(deep));
    l=0,r=0;
    deep[s]=1;q[++r]=s;
    while(l!=r)
    {
        int u=q[++l];
        for(int i=head[u];~i;i=e[i].nxt)
        {
            int v=e[i].to;
            if(!deep[v]&&e[i].flow)
            {
                deep[v]=deep[u]+1;
                q[++r]=v;
            }
        }
    }
    return deep[t];
}
int dfs(int u,int min_f)
{
    if(u==t)return min_f;
    int temp_f=0;
    for(int i=head[u];~i&&min_f;i=e[i].nxt)
    {
        int v=e[i].to;
        if(deep[v]==deep[u]+1&&e[i].flow)
        {
            int d=dfs(v,min(min_f,e[i].flow));
            if(d>0)
            {
                min_f-=d;
                temp_f+=d;
                e[i].flow-=d;
                e[i^1].flow+=d;
            }
        }
    }
    if(!temp_f)deep[u]=-1;
    return temp_f;
}
int dinic()
{
    int res=0;
    while(bfs())
        res+=dfs(s,INF);
    return res;
}
vector<int>path;
void print(int u)
{
    if(visit[u])return;
    visit[u]=1;
    path.push_back(u);
    for(int i=head[u];~i;i=e[i].nxt)
    {
        int v=e[i].to;
        if(e[i].flow==0&&v>n&&v<=2*n&&!visit[v])
            print(v-n);
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    memset(head,-1,sizeof(head));
    s=0,t=2*n+2;
    for(int i=1;i<=n;i++)
        add_edge(s,i,1);
    for(int i=n+1;i<=2*n;i++)
        add_edge(i,t,1);
    for(int i=1;i<=m;i++)         //注意这里连边的时候要将x与y拆分出来的那个点相连
    {
        int x,y;scanf("%d%d",&x,&y);
        add_edge(x,y+n,1);
    }
    int ans=dinic();
    for(int i=1;i<=n;i++)
    {
        print(i);
        if(path.size())     //输出路径
        {
            for(int i=0;i<path.size();i++)
                printf("%d%c",path[i],i==path.size()-1?'\n':' ');
            path.clear();
        }
    }
    printf("%d\n",n-ans);
    return 0;
}

 

posted @ 2020-02-28 14:36  grass_lin  阅读(301)  评论(0编辑  收藏  举报