最小路径覆盖

· · 题解

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

简单证明:一开始每个点都独立的为一条路径,总共有n条不相交路径。我们每次在二分图里加一条边就相当于把两条路径合成了一条路径,因为路径之间不能有公共点,所以加的边之间也不能有公共点,这就是匹配的定义。所以有:最小路径覆盖数=顶点数-最大匹配。

有这个定理就简单了,求一遍最大匹配就好了。

1.把每个点拆成两个点:入点和出点。

2.建一个超级源点,向每个出点连一条流量为1的边

3.建一个超级汇点,每个入点向它连一条流量为1的边

4.根据给的边(i,j),把i的出点连向j的入点

最后是输出路径,在网络流跑完后的残图中找为0的边,用并查集维护一下就好了。

#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
const int inf=1e9;
int n,m,x,y,z,s,t,ans,d[10005];
struct node{
    int next,to,w;
}a[500000];
int cnt=1,head[10005],cur[10005],f[10005],vis[10005];
queue <int> q;
void add(int x,int y,int dis)
{
    a[++cnt].next=head[x];
    a[cnt].to=y;
    a[cnt].w=dis;
    head[x]=cnt;
}
int find(int u)
{
    if(f[u]==u) return u;
    else return f[u]=find(f[u]);
}
bool bfs(int s,int t)
{
    memset(d,0x7f,sizeof(d));
    while(!q.empty()) q.pop();
    for(int i=0;i<=2*n+1;i++) cur[i]=head[i];
    d[s]=0;
    q.push(s);
    while(!q.empty())
    {
        int u=q.front();q.pop();
        for(int i=head[u];i;i=a[i].next)
        {
            int v=a[i].to;
            if(d[v]>inf&&a[i].w) 
            {
                d[v]=d[u]+1;
                q.push(v);
            }
        }
    }
    if(d[t]<inf) return true;
    else return false;
}
int dfs(int now,int t,int limit)
{
    if(!limit||now==t) return limit;
    int flow=0,f;
    for(int i=cur[now];i;i=a[i].next)
    {
        cur[now]=i;
        int v=a[i].to;
        if(d[v]==d[now]+1&&(f=dfs(v,t,min(limit,a[i].w))))
        {
            flow+=f;
            limit-=f;
            a[i].w-=f;
            a[i^1].w+=f;
            if(!limit) break;
        }
    }
    return flow;
}
void output(int x)
{
    printf("%d ",x);
    vis[x]=1;
    for(int i=head[x];i;i=a[i].next)
        if(a[i].w==0&&a[i].to>n)
            output(a[i].to-n);
}
int main()
{
    scanf("%d%d",&n,&m);
    s=0;t=2*n+1;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        add(x,y+n,1);   //把x的出点和y的入点连边
        add(y+n,x,0);
    }
    for(int i=1;i<=n;i++)
        add(0,i,1),add(i,0,0),add(i+n,t,1),add(t,i+n,0);
        //建一个超级源点,向每个出点连一条流量为1的边;建一个超级汇点,每个入点向它连一条流量为1的边
    while(bfs(s,t)) ans+=dfs(s,t,inf);//跑一遍网络流
    for(int i=1;i<=n;i++) f[i]=i;   
    for(int i=0;i<=cnt;i++) //并查集维护
        if(a[i].next>=1&&a[i].next<=n&&a[i].to>n&&a[i].to<t&&a[i].w==0)
            f[find(a[i].to-n)]=find(a[i].next);
    for(int i=1;i<=n;i++)
        if(find(i)==i&&(!vis[i]))
            output(i),printf("\n");//输出路径
    printf("%d",n-ans);
    return 0;
}