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

最近肝网络流有点上头
其实就是想水博客不想做题
博客前废话完毕
作为(luo gu)经典的网络瘤24题之一当然要写个博客了
传送


题目到底在说啥?

现在有一个DAG.选出图上任意条路径,每选出一条路径,该路径上的点被覆盖的次数+1,使得所有点都被覆盖且任意一个点只被覆盖一次。选出的路径的数量就是一个路径覆盖。最小路径覆盖就是最少的路径的数量。

举个简单的栗子

显然最短路径覆盖是1

就是红色的路径辣

如果给我们的是一张十分复杂的图怎么办?
路径覆盖这东西最大也就是n-1对叭?(暴力的选出类似树一样的东西,它有n-1条边)
我们考虑让路径覆盖变小一点
如果我们选出来的边有a-->b和b-->c的边,那么它们就可以合并成一条路径,从而使答案-1
当然要注意如果此时还有b-->d的边,那么a-->b只能从b-->c和b-->d中选一个合并
那么最小路径覆盖就是把边不断合并,一直合并到无法再合并为止

显然我们得把这个东西和网络流联系一下(不然怎么叫网络瘤24题)
想到网络流可以搞二分图匹配
接着想到我们有拆点这个操作

拆点操作是个啥

就是把一个点拆成入点和出点两个点,出点的只有出度没有入度,入点只有入度没有出度。
说白了就是这个点出去的边从出点出,同时这个点作为终点的边是连向入点

搞完出点和入点之后发现这是一个二分图

有了这个神奇的操作,我们每次合并一条边,就是选出来一个匹配。我们要合并尽可能多的边,就是选出来尽可能多的匹配,也就是二分图最大匹配问题

求二分图最大匹配,可以用匈牙利算法这种好东西(还顺带记录了路径),but这里是,所以我们考虑用网络流搞。

上面已经搞定了建图(也就相当于搞定了网络流),接下来就是毒瘤的记录路径了

考虑到建图的时候入点的编号是点的真实编号+n,所以可以预处理ys(映射)数组,ys[i]为i点的真实编号(也就是说当i>n时,ys[i]=i-n)

同时考虑到网络流的原理,如果有一条边的残量(也就是dis)为0,则说明最终我们选择了这个匹配,也就是说与该边相连的点在同一条路径上。这样输出路径就可以用dfs实现。注意在dfs的时候也要考虑那些反向边。

直接贴代码叭

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
const int inf=214748364;
typedef long long ll;
inline int read()
{
    char ch=getchar();
    int x=0;bool f=0;
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')f=1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return f?-x:x;
}
int n,m,head[409],cnt=1,ans,s,t;
struct E{
    int to,nxt,dis;
}ed[12009];
int dep[409],cur[409];
queue <int> q;
int ys[409];
bool vis[159];
inline void add(int fr,int to,int dis)
{
    cnt++;
    ed[cnt].to=to;
    ed[cnt].dis=dis;
    ed[cnt].nxt=head[fr];
    head[fr]=cnt;
}
inline bool bfs()
{
    for(int i=1;i<=2*n+2;i++)
     dep[i]=0;
    for(int i=1;i<=2*n+2;i++)
     cur[i]=head[i];
    dep[s]=1; 
    q.push(s);
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        for(int e=head[u];e;e=ed[e].nxt)
        {
            int v=ed[e].to;
            if(ed[e].dis&&!dep[v])
            {
                dep[v]=dep[u]+1;
                q.push(v);
            }
        }
    }
    return dep[t];
}
int dfs(int now,int in)
{
    if(now==t)return in;
    int out=0;
    for(int e=cur[now];e;e=ed[e].nxt)
    {
        cur[now]=e;
        int v=ed[e].to;
        if(ed[e].dis&&dep[v]==dep[now]+1)
        {
        int ret=dfs(v,min(in,ed[e].dis));
            ed[e].dis-=ret;
            ed[e^1].dis+=ret;
            in-=ret;
            out+=ret;
            if(!in) return out;
        }
    }
    return out;
}
inline void dinic()
{
    int ret;
    while(bfs())
    {
         ret=0;
         while(ret=dfs(s,20000000))ans+=ret;
    }
}
//上面全是网络流
void lj(int u)//路径不是辣鸡 !!!
{
	 printf("%d ",u);
	vis[u]=1;//vis记录是否已经输出
	for(int e=head[u];e;e=ed[e].nxt)
	{
		int v=ed[e].to;
		if(v==s||v==t)continue;//这里注意不考虑自己加的超源,超汇
		if(!ed[e].dis&&!vis[ys[v]])
		  lj(ys[v]); 
	}
}
int main()
{
    n=read();m=read();s=n*2+1;t=n*2+2;
    for(int i=1;i<=m;i++)
    {
        int fr=read(),to=read();
        add(fr,to+n,1);add(to+n,fr,0);
    }
    for(int i=1;i<=n;i++)
     add(s,i,1),add(i,s,0);
    for(int i=n+1;i<=2*n;i++)
     add(i,t,1),add(t,i,0);
    for(int i=1;i<=n;i++)
     ys[i]=i;
    for(int i=n+1;i<=2*n;i++)
	 ys[i]=i-n;
	ys[s]=s;ys[t]=t;
	dinic();
    ans=n-ans;
    for(int i=1;i<=n;i++)//这里就不从超源开始考虑了qwq
    {
    	if(!vis[i])
		{
			lj(i);
    	printf("
");
		}
	}
    printf("%d",ans);
}
原文地址:https://www.cnblogs.com/lcez56jsy/p/11406174.html