• 网络最大流入门


    听说网络流的时代要来临了

    概念

    在有向图(G(V,E))中:

    仅有一个入度为(0)的点(S),称为源点

    仅有一个出度为(0)的点(T),称为汇点

    每条边权值非负,称为该边的容量,记作(c(u,v))

    弧的流量:容量网络中每条边的实际流量,记作(f(u,v))

    网络流:容量网络中所有弧上流量的集合(f={f(u,v)})

    残量网络:所有点和没有满流的边构成的图被称为残量网络

    增广路:残量网络中从源点(S)到汇点(T)的路径被称为增广路

    零流:网络流上每条弧上流量都为(0)

    伪流:只满足容量限制,不满足流守恒的网络流(据说预流推进有用)

    性质

    1.容量限制:每条边的实际流量(f(u,v))不超过它的容量(c(u,v))(c(u,v)-f(u,v))被称为剩余流量

    2.斜对称:(f(u,v)=-f(v,u))

    3.流守恒:除了源点和汇点之外,其余各点流入和流出的流量相等

    EK算法

    基本思想:通过(BFS)不断寻找增广路并添加回流求出最大流

    首先我们解释怎么进行回流:

    现在我们有一个画的很丑的网络流图……

    我们在上面添加反向边,边权全部为0:

    然后通过bfs找增广路,假设找到了蓝色这一条:

    (emm)我们发现路径上最小流量为(1),我们把答案(+1),把蓝色路线上的流量全部(-1),然后就没法继续增广了

    但是显然答案应该是(2)

    这时我们应该将路线上反向边流量(+1)

    此时我们继续寻找增广路,会发现另外一条

    再将答案(+1)就得到正确结果了

    我们发现对于中间那条边,我们正向流过一次,反向流过一次,然后流量恢复到了初始时刻的状态

    也就是说反向边给了网络流一个反悔的机会,可以让边上的流量退回到以前的状态,相当于让两条本不应该交叉的增广路变成合法状态

    可以证明:每次增广都会使得流量增加,且增加次数与流量大小无关,是多项式级别的复杂度

    其实EK算法复杂度上界是(O(nm^2)),实际复杂度一般达不到上界

    Dinic算法

    但是(EK)算法复杂度仍然有点高,最高可达(O(n^5)),我们对其进行优化

    观察到在(EK)算法中我们每次寻找一条增广路,效率较低,我们尝试每次寻找多条增广路

    (Dinic)算法是在残量网络的分层图上(DFS)寻找增广路的算法

    规定节点的层是该点到源点的最短距离,原图中所有的点和连接不同层的点之间的边(未饱和弧)构成的子图称为分层图

    我们每次用(BFS)在残量网络上构造分层图,然后通过(DFS)在分层图上寻找增广路。改变流量过程与(EK)算法一致,直到无法找到新的增广路为止

    理论复杂度上界为(O(n^2m))但是法律规定不许卡dinic

    但是某谷的模板题(n^2m)上界达到(10^{13})也有点过分了吧

    展开查看
    
    ```cpp
    #include
    #include
    #include
    #include
    #include
    #include
    using namespace std;
    namespace red{
    #define int long long
    #define eps (1e-8)
    	inline int read()
    	{
    		int x=0;char ch,f=1;
    		for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    		if(ch=='-') f=0,ch=getchar();
    		while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    		return f?x:-x;
    	}
    	const int N=10010,inf=0x3f3f3f3f;
    	int n,m,e,ret,tim,st,ed;
    	int head[N],cnt=1;//记得从1开始,不然异或一下就变成0了
    	struct point
    	{
    		int nxt,to,val;
    		point(){}
    		point(const int &nxt,const int &to,const int &val):nxt(nxt),to(to),val(val){}
    	}a[N<<5];
    	inline void link(int x,int y,int z)
    	{
    		a[++cnt]=(point){head[x],y,z};head[x]=cnt;
    		a[++cnt]=(point){head[y],x,0};head[y]=cnt;
    	}
    	int dis[N];
    	queue q;
    	inline bool bfs()
    	{
    		memset(dis,0,sizeof(dis));//分层
    		q.push(st);dis[st]=1;//初始不赋成1会锅
    		while(!q.empty())
    		{
    			int now=q.front();
    			q.pop();
    			for(int i=head[now];i;i=a[i].nxt)
    			{
    				int t=a[i].to;
    				if(!dis[t]&&a[i].val)//下个点没被分过层且这条弧还有用
    				{
    					dis[t]=dis[now]+1;
    					q.push(t);
    				}
    			}
    		}
    		return dis[ed];
    	}
    	inline int dfs(int now,int c)
    	{
    		if(now==ed||!c) return c;//到达汇点或者没流量了
    		int ret=c,f;
    		for(int i=head[now];i;i=a[i].nxt)
    		{
    			int t=a[i].to;
    			if(dis[t]==dis[now]+1)
    			{
    				f=dfs(t,min(ret,a[i].val));
    				ret-=f;
    				a[i].val-=f;
    				a[i^1].val+=f;
    				if(!ret) return c;//当前流量流完了
    			}
    		}
    		if(ret==c) dis[now]=0;//废点,没法增广
    		return c-ret;
    	}
    	inline int dinic()
    	{
    		int ret=0;
    		while(bfs()) ret+=dfs(st,inf);
    		return ret;
    	}
    	inline void main()
    	{
    		n=read(),m=read(),st=read(),ed=read();
    		for(int x,y,z,i=1;i<=m;++i)
    		{
    			x=read(),y=read(),z=read();
    			link(x,y,z);
    		}
    		printf("%lld
    ",dinic());
    	}
    }
    signed main()
    {
    	red::main();
    return 0;
    }
    ```
    

    当前弧优化

    考虑我们(dfs)的过程必然是将某一条边榨干之后再返回,所以这条边其实已经没有用了,我们下次不需要再访问

    增加cur数组,每次(BFS)的时候赋值成(head)

    for(int i=1;i<=n;++i)
    {
    	dis[i]=0;
    	cur[i]=head[i];
    }
    

    然后(DFS)的时候再记录

    for(int i=cur[now];i;i=a[i].nxt)
    {
    	cur[now]=i;	
    	int t=a[i].to;
    	if(dis[t]==dis[now]+1)
    	{
    		f=dfs(t,min(ret,a[i].val));
    		ret-=f;
    		a[i].val-=f;
    		a[i^1].val+=f;
    		if(!ret) return c;//当前流量流完了
    	}
    }
    
    最大流一般用来构造一个图然后判断是否满足某些条件,如果最大流达到某个数值说明满足
    
    
    

    最小鸽

    已知图(G(V,E))是网络流图,假设(g)(V)的一个子集,而且(g)满足:(Sin g,T otin g),这样(g)把顶点分成两部分

    割的定义:起点在(g),终点在(overline{g})的边,所组成的集合,为割,记作((g,overline{g}))

    ((g,overline{g}))中所有边容量的集合称为割的容量,记作(C(g,overline{g}))

    最大流最小鸽定理:

    网络流的最大流等于最小鸽的容量

    证明:不会

    但是可以感性理解一下:最大流由增广路上容量最小的边限制,而最小鸽必定是由所有鸽中容量之和最小的一组割构成

  • 相关阅读:
    Atitit 华为基本法 attilax读后感
    Atitit 华为管理者内训书系 以奋斗者为本 华为公司人力资源管理纲要 attilax读后感
    Atitit 项目版本管理gitflow 与 Forking的对比与使用
    Atitit 管理的模式扁平化管理 金字塔 直线型管理 垂直管理 水平管理 矩阵式管理 网状式样管理 多头管理 双头管理
    Atitit 乌合之众读后感attilax总结 与读后感结构规范总结
    深入理解 JavaScript 异步系列(4)—— Generator
    深入理解 JavaScript 异步系列(3)—— ES6 中的 Promise
    深入理解 JavaScript 异步系列(2)—— jquery的解决方案
    深入理解 JavaScript 异步系列(1)——基础
    使用 github + jekyll 搭建个人博客
  • 原文地址:https://www.cnblogs.com/knife-rose/p/12094721.html
Copyright © 2020-2023  润新知