• Luogu 2764 最小路径覆盖问题 / Libre 6002 「网络流 24 题」最小路径覆盖 (网络流,最大流)


    Luogu 2764 最小路径覆盖问题 / Libre 6002 「网络流 24 题」最小路径覆盖 (网络流,最大流)

    Description

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

    Input

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

    Output

    第1行开始,每行输出一条(字典序)路径。最后一行是最少路径数。

    Sample Input

    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

    Sample Output

    1 4 7 10 11
    2 5 8
    3 6 9
    3

    Http

    Luogu:https://www.luogu.org/problem/show?pid=2764
    Libre:https://loj.ac/problem/6002

    Source

    网络流,最大流

    解决思路

    这题是sdoi2010星际竞速的弱化版,去掉了费用的限制。
    对于每一个点u,我们把它拆成两个点u和u+n,分别作为入点和出点。在源点与所有入点之间连一条容量为1的边,在所有的出点与汇点之间也连一条容量为1的边。再对于每一条有向边u->v,我们连接u的入点和v的出点,容量为1.
    这样建图的目的是保证任何一个点只进去一次,出来一次,并且每个点至少都要走一次。
    至于如何找出路径呢?
    假设我们现在选择第u个点出发,则寻找与其相连的所有边中满足相连的点v为出点且残量为0,因为我们保证了一个点只走一次,所以这个点一定是唯一的。然后再以v为出发点寻找,直到找不到为止。需要注意的是,为了防止重复计算,上面我们经过的所有点都要标记一下,后面就不再经过了。
    另:这里使用Dinic实现最大流,关于Dinic算法,请移步我的这篇文章

    代码

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    const int maxN=500;
    const int maxM=60000;
    const int inf=2147483647;
    
    class Edge
    {
    public:
    	int v,flow;
    };
    
    int n,m;
    int cnt=-1;
    int Head[maxN];
    int Next[maxM];
    Edge E[maxM];
    int depth[maxN];
    int Q[maxM];
    int cur[maxN];
    bool vis[maxN];
    
    void Add_Edge(int u,int v,int flow);
    bool bfs();
    int dfs(int u,int flow);
    
    int main()
    {
    	memset(Head,-1,sizeof(Head));
    	scanf("%d%d",&n,&m);
    	for (int i=1;i<=n;i++)
    	{
    		Add_Edge(0,i,1);//连接源点与入点
    		Add_Edge(i+n,2*n+1,1);//连接出点与汇点
    	}
    	for (int i=1;i<=m;i++)
    	{
    		int u,v;
    		scanf("%d%d",&u,&v);
    		Add_Edge(u,v+n,1);//连接u的入点与v的出点
    	}
    	while (bfs())
    	{
    		for (int i=0;i<=n*2+1;i++)//当前弧优化
    			cur[i]=Head[i];
    		while (int di=dfs(0,inf));
    	}
    	memset(vis,0,sizeof(vis));
    	int Ans=0;
    	for (int i=1;i<=n;i++)//统计路径
    	{
    		if (vis[i]==1)
    			continue;
    		int now=i;
    		bool get;
    		do
    		{
    			cout<<now<<" ";
    			vis[now]=1;
    			get=0;
    			for (int i=Head[now];i!=-1;i=Next[i])
    				if ((E[i].flow==0)&&(E[i].v>n)&&(E[i].v<=2*n))
    				{
    					now=E[i].v-n;
    					get=1;
    					break;
    				}
    		}
    		while (get==1);
    		cout<<endl;
    		Ans++;
    	}
    	cout<<Ans<<endl;//输出路径条数
    	return 0;
    }
    
    void Add_Edge(int u,int v,int flow)//加边
    {
    	cnt++;
    	Next[cnt]=Head[u];
    	Head[u]=cnt;
    	E[cnt].v=v;
    	E[cnt].flow=flow;
    
    	cnt++;
    	Next[cnt]=Head[v];
    	Head[v]=cnt;
    	E[cnt].v=u;
    	E[cnt].flow=0;
    }
    
    bool bfs()//bfs求层次图
    {
    	memset(depth,-1,sizeof(depth));
    	int t=0,h=1;
    	Q[1]=0;
    	depth[0]=1;
    	do
    	{
    		t++;
    		int u=Q[t];
    		for (int i=Head[u];i!=-1;i=Next[i])
    		{
    			int v=E[i].v;
    			if ((E[i].flow>0)&&(depth[v]==-1))
    			{
    				depth[v]=depth[u]+1;
    				h++;
    				Q[h]=v;
    			}
    		}
    	}
    	while (h!=t);
    	if (depth[n*2+1]==-1)//当汇点不存在层次图中时,说明增广完毕
    		return 0;
    	return 1;
    }
    
    int dfs(int u,int flow)//dfs增广
    {
    	if (u==n*2+1)
    		return flow;
    	for (int &i=cur[u];i!=-1;i=Next[i])
    	{
    		int v=E[i].v;
    		if ((depth[v]==depth[u]+1)&&(E[i].flow>0))
    		{
    			int di=dfs(v,min(flow,E[i].flow));
    			if (di>0)
    			{
    				E[i].flow-=di;
    				E[i^1].flow+=di;
    				return di;
    			}
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    数梦工场:新思维、新技术下的互联网+政务
    计算成就价值_数据实现梦想——达科在DT时代转型历程的分享
    AliSQL开源功能特性
    mysql 索引的使用
    sql经典面试题
    数据库理论知识点
    sql语句面试练习
    数据库范式的选择使用
    sql常用语句
    数据库范式
  • 原文地址:https://www.cnblogs.com/SYCstudio/p/7267621.html
Copyright © 2020-2023  润新知