网络流最大流入门(洛谷P3376)
来源:互联网 发布:淘宝swatch是真的吗 编辑:程序博客网 时间:2024/06/04 19:42
网络流定义
所谓网络或容量网络指的是一个连通的赋权有向图 D= (V、E、C) , 其中V 是该图的顶点集,E是有向边(即弧)集,C是弧上的容量。此外顶点集中包括一个起点和一个终点。
形象点说,可以把每条边比作一个水管,每个水管都有一个流量上界(即最多能通过多少水)与当前流量(即当前流过多少水),而网络流指的就是类似的这样一张图。
最大流问题
定义
给你一个源点(可以把它看做水源)以及一个汇点(可以把它看做水池),求从源点到汇点的最大流量。
求解最大流
求解最大流有两种方法:
EK算法
先介绍几个定义:
残量网络:即边i剩余的流量。
后向弧:即边i的反向边,它的流量上界为0。
增广路:即从源点到汇点的一条路径,满足经过的边残量网络均>0.
EK算法就是不停地找增广路,每找到一条就修改路径上边的流量,直到找不到增广路为止。此时的总量即为答案。
看上去很暴力对不对?
但是因为一般情况找增广路不会太多次就没有增广路了,因此复杂度玄学。
①找增广路
找增广路时用到了BFS,每找到一条残量网络>0的边,就把它所指向的节点加入队列中。如果指向的是汇点就直接返回答案。如果做到队空则说明没有增广路。
int bfs(int now){//增广路,now表示源点 memset(f,false,sizeof(f)); int r=0,w=1; que[1]=now; f[now]=true; rem[s]=0x7fffffff;//刚开始把源点的流量改为∞ while (r<w){ int x=que[++r]; for (int i=h[x];~i;i=ed[i].next){ if (!f[ed[i].to]&&ed[i].v>ed[i].flow){//如果当前残量网络大于0 f[ed[i].to]=true; que[++w]=ed[i].to;//加入队列 fa[ed[i].to].x=x; fa[ed[i].to].e=i; rem[ed[i].to]=min(rem[x],ed[i].v-ed[i].flow);//当前最小残量网络 if (ed[i].to==t) return rem[t]; } } } return 0;}
②修改路径流量
修改路径流量时用到了后向弧。如果经过这条边,就把它的流量加上当前得到的答案,把它的对应边减去当前得到的答案。因为如果经过了后向弧则说明它往回走了,此时应减小流量。
void change(int remain){//remain表示当前增广出来的答案 int now=t; while (now!=s){ int e=fa[now].e; ed[e].flow+=remain;//增加当前边的流量 ed[e^1].flow-=remain;//减少对应边的流量 now=fa[now].x; }}
算法模板:
以洛谷P3376为例:
#include<cstdio>#include<cstring>#include<algorithm>#define MAXN 100000#define MAXM 1000000using namespace std;struct edge{ int next,to; int flow,v;};struct father{ int x,e;};int n,m,k,s,t;int h[MAXN+5];edge ed[2*MAXM+5];father fa[MAXN+5];int que[MAXN+5],rem[MAXN+5];bool f[MAXN+5];inline char readc(){ static char buf[100000],*l=buf,*r=buf; if (l==r) r=(l=buf)+fread(buf,1,100000,stdin); if (l==r) return EOF; return *l++;}inline int _read(){ int num=0; char ch=readc(); while (ch<'0'||ch>'9') ch=readc(); while (ch>='0'&&ch<='9') { num=num*10+ch-48; ch=readc(); } return num;}void addedge(int x,int y,int z){ ed[k].next=h[x]; ed[k].to=y; ed[k].v=z; h[x]=k++; ed[k].next=h[y]; ed[k].to=x; ed[k].v=0; h[y]=k++;}int bfs(int now){ memset(f,false,sizeof(f)); int r=0,w=1; que[1]=now; f[now]=true; rem[s]=0x7fffffff; while (r<w){ int x=que[++r]; for (int i=h[x];~i;i=ed[i].next){ if (!f[ed[i].to]&&ed[i].v>ed[i].flow){ f[ed[i].to]=true; que[++w]=ed[i].to; fa[ed[i].to].x=x; fa[ed[i].to].e=i; rem[ed[i].to]=min(rem[x],ed[i].v-ed[i].flow); if (ed[i].to==t) return rem[t]; } } } return 0;}void change(int remain){ int now=t; while (now!=s){ int e=fa[now].e; ed[e].flow+=remain; ed[e^1].flow-=remain; now=fa[now].x; }}int maxflow(){ int ans=0; while (1){ int sum=bfs(s); if (!sum) return ans; ans+=sum; change(sum); }}int main(){ memset(h,-1,sizeof(h)); n=_read(); m=_read(); s=_read(); t=_read(); for (int i=1;i<=m;i++){ int u=_read(),v=_read(),d=_read(); addedge(u,v,d); } printf("%d\n",maxflow()); return 0;}
Dinic算法
dinic就是在增广路上进行了改进。它运用到了分层图的思想,先BFS进行分层,再DFS增广。每次增广时仅当它指向的节点的层次=该节点的层次+1时进行增广。增广同时修改流量。
①BFS分层
同EK一样,只是多求了一个层次而已。
bool bfs(){ memset(f,false,sizeof(f)); int r=0,w=1; dis[s]=0; f[s]=true; que[1]=s; while (r<w){ int x=que[++r]; for (int i=h[x];~i;i=ed[i].next) if (!f[ed[i].to]&&ed[i].v>ed[i].flow){ dis[ed[i].to]=dis[x]+1; f[ed[i].to]=true; que[++w]=ed[i].to; } } return f[t];}
②DFS增广
具体见注释:
int dfs(int x,int rem){//x为当前节点,rem为当前最小残量 if (x==t||rem==0) return rem;//如果已经到汇点了或者残量为0就直接返回 int sum=0; for (int &i=cop[x];~i;i=ed[i].next)//直接从上次做过的地方做 if (dis[ed[i].to]==dis[x]+1){//分层图 int p=dfs(ed[i].to,min(ed[i].v-ed[i].flow,rem)); if (p){//如果找到了 sum+=p; ed[i].flow+=p; ed[i^1].flow-=p; rem-=p;//修改 } } return sum;}
算法模板
仍然是洛谷P3376
#include<cstdio>#include<cstring>#include<algorithm>#define MAXN 100000#define MAXM 1000000using namespace std;struct edge{ int next,to,v,flow;};int n,m,s,t,k;int h[MAXN+5],dis[MAXN+5],cop[MAXN+5],que[MAXN+5];edge ed[MAXM*2+5];bool f[MAXN+5];inline char readc(){ static char buf[100000],*l=buf,*r=buf; if (l==r) r=(l=buf)+fread(buf,1,100000,stdin); if (l==r) return EOF; return *l++;}inline int _read(){ int num=0; char ch=readc(); while (ch<'0'||ch>'9') ch=readc(); while (ch>='0'&&ch<='9') { num=num*10+ch-48; ch=readc(); } return num;}void addedge(int x,int y,int z){ ed[k].next=h[x]; ed[k].to=y; ed[k].v=z; h[x]=k++; ed[k].next=h[y]; ed[k].to=x; ed[k].v=0; h[y]=k++;}bool bfs(){ memset(f,false,sizeof(f)); int r=0,w=1; dis[s]=0; f[s]=true; que[1]=s; while (r<w){ int x=que[++r]; for (int i=h[x];~i;i=ed[i].next) if (!f[ed[i].to]&&ed[i].v>ed[i].flow){ dis[ed[i].to]=dis[x]+1; f[ed[i].to]=true; que[++w]=ed[i].to; } } return f[t];}int dfs(int x,int rem){ if (x==t||rem==0) return rem; int sum=0; for (int &i=cop[x];~i;i=ed[i].next) if (dis[ed[i].to]==dis[x]+1){ int p=dfs(ed[i].to,min(ed[i].v-ed[i].flow,rem)); if (p){ sum+=p; ed[i].flow+=p; ed[i^1].flow-=p; rem-=p; } } return sum;}int maxflow(){ int ans=0; while (bfs()){ memcpy(cop,h,sizeof(cop)); ans+=dfs(s,0x7fffffff); } return ans;}int main(){ memset(h,-1,sizeof(h)); n=_read(); m=_read(); s=_read(); t=_read(); for (int i=1;i<=m;i++){ int u=_read(),v=_read(),d=_read(); addedge(u,v,d); } printf("%d\n",maxflow()); return 0;}
- 网络流最大流入门(洛谷P3376)
- P3376 网络最大流
- 【模板】ISAP网络最大流 (模板题:洛谷P3376)
- 洛谷 P3376【模板】网络最大流
- 洛谷P3376【模板】网络最大流
- 洛谷 P3376 【模板】网络最大流
- P3376 【模板】网络最大流
- 【洛谷P3376】最大流模板(dinic)
- Dinic算法 P3376 【模板】网络最大流
- HDU 3549(网络流入门之最大流)
- 网络流入门—用于最大流的Dinic算法
- 图论 - 网络流_最大流入门题
- 网络流入门—用于最大流的Dinic算法
- [网络流入门,简单题]poj1273Drainage Ditches最大流
- poj 1273 Drainage Ditches(网络流入门之最大流)
- 网络流入门—用于最大流的Dinic算法
- 网络流入门—用于最大流的Dinic算法
- PowerOJ 1679: Drainage Ditches(网络流入门) 最大流模板
- [RK3288][Android6.0] 调试笔记 --- RT5640播放时的Codec寄存器列表
- Java final 简单用法
- 用n元钱买橙子,6元/8元一包,不零卖。设计函数返回最少袋数或-1
- mysql group by中没有的字段 select中有 的情况说明
- 探新Web前端开发(二)
- 网络流最大流入门(洛谷P3376)
- java生成图片验证码
- net.sf.json.JSONException: java.lang.reflect.InvocationTargetException
- getInstance
- 多渠道打包和一键完成(全部产品)打包并签名
- Thrift和Spring集成
- Android Orientation Sensor
- qt常用代码段(获取ip,MAC,username,hostname)
- 异常和文件操作