【图论】网络流+Dinic算法
引入
(Pecco)
流,顾名思义,就像水流或电流,也具有它们的性质。如果把网络想象成一个自来水管道网络,那流就是其中流动的水。每条边上的流不能超过它的容量,并且对于除了源点和汇点外的所有点(即中继点),流入的流量都等于流出的流量。
图论的基本表示
V(vertex)代表点集。
E(edge)代表边集。
C:代表容量
定义
源点:起始点(源头)
汇点:汇聚点(入海口)
流量:实际流经路径的量
容量:流量上限
入流:流入这个点的量(源点不具有入流)
出流:流出这个点的量(汇点不再具有出流)
残量:剩余流量=流量上限-实际流量
网络流:在一张有向图中,规定一个源点和一个汇点,并且组成从源点到达汇点的所有路径中的边都有一个流量上限,并且所有流经的点实际流入该点的容量等于实际流出的容量,那么则称这张有向图为网络流。
(可以将网络流理解成是一个埋在地下的水管道地图)
二分图相关的定义
点和点有存在边来连接他两,这是一个客观事实,这就叫做非匹配边(存在但没有进行匹配的边,备胎),但最终是由人或者机器来完成确定的过程,而其存在得到了最终确定的边叫做匹配边。
而在确定最终匹配的结果,我们需要借助交替路和增广路这两种定义来完成匹配这个过程。
交错路:
(alternating path) 始于非匹配点且由匹配边与非匹配边交错(一下子非匹配边一下子匹配边)而成。
注意:交错路是路,是由好几条连通的边组成的。
增广路:
(augmenting path)是始于非匹配点且终于非匹配点的交错路
由于增广路本身是交错路的性质,且整个增广路上拥有两个非匹配点,我们可以激活非匹配边,关闭匹配边,以达到更大的匹配数量。
分层图:将根节点标号为1,从根节点向外跑bfs,每一个点的标号等于前一个结点的标号+1(不考虑被重复标记?),这些点的标号代表着对应的层数。
几个基本性质
性质一:
总流量小于等于总容量。
- 碗里面盛的水总会比碗来得小吧!
性质二:
对于网络流上的某个结点来说,流入该点的流量等于流出该点的流量。
(sum_{uin V}cur[u][x]=sum_{vin V}cur[x][v])
其中cur[i][j] 代表从结点i流向结点j的流量。
性质三:
由于流是有方向的,算的上是一个向量,当我们说从一个结点i流向结点j有x个量时,换句话说,也就是说从结点j流向结点i有-x个量。
网络流算法的组成
建边
最好不要多开几个数组来记录边的各个信息。
可采用结构体对边的信息做一个统计
struct node{
int u,v;//起点和终点
int flow,cap;
int nxt;
}edge[MAXN];
边的信息 | 变量名 | 注释 |
---|---|---|
起点 | u | |
终点 | v | |
容纳量 | cap | capacity |
流量 | flow | |
下一条同属起点u的边 | nxt | next |
int head[MAXN],idx = 0;
void add_edge(int a,int b,int c)
{
edge[idx].u=x;
edge[idx].v=y;
edge[idx].cap=c;
edge[idx].flow=0;
edge[idx].nxt=head[a];
head[a]=idx++;
}
bfs
- 规划好层次,优化dfs的实际时间。
- bfs可以用来判定到底可不可以到达终点。
bool bfs(int bg,int ed)
{
memset(dis,-1,sizeof(dis));
queue<int> q;
q.push(bg);
dis[bg]=0;
while(q.size())
{
int t=q.front();
q.pop();
for(int i = head[t];~i;i=edge[i].nxt)
{ if(dis[edge[i].v]==-1&&edge[i].cap>edge[i].flow)//如果深度还没有被跑过的话(队列的存法保证深度的选择是最优的)且容量没有用干净
{
dis[edge[i].v] = dis[edge[i].u]+1;
q.push(edge[i].v);
}
}
}
if(dis[ed]==-1) //如果到达不了终点的话,就代表没有任何办法流向终点
return 0;
else return 1;
}
Dinic
void Dinic(int S,int T)
{
int ansflow = 0;
int cs = 0;
int maxflow = 0;
for(int i =1;i<=n;i++)
cur[i] = head[i];
while(bfs(S,T))
{
int p = dfs(S,maxn);//传入一个假定的最大值,到具体的空间内再进行修正
cs++;//流的数量
ansflow+=p;//总的流量
maxflow=max(maxflow,p);//更新最大流
}
if(ansflow==0)
//无法通过
else
//输出最大的流量
}
dfs
- 预设的流会因为后面的管道的受限而不断修改预定的值
- res表示的是从上一个管道流向该点的流量
- 如果说要流向的下一个管道仍然有空位可以流的话,那么就尽可能地将当前剩余的量尽可能给塞进去。
- 塞进去后要记得修改该条边和反向边的流量
- dfs的目的是用来更新流的使用情况
int dfs(int cur,int res)
{
if(cur==T||res<=0)
return res;
int flow=0,f;
for(int i=head[cur];~i;i=ne[i])
{
if(dis[cur]+1==dis[edge[i].v]&&edge[i].cap-edge[i].flow>0)
{
f=dfs(edge[i].v,min(res,edge[i].cap-edge[i].flow>0));
edge[i].flow+=f;
edge[i^1].flow-=f;
flow+=f;
res-=f;
if(res<=0) break;
}
}
return flow;
}
网络流的问题种类
网络流最大流
网络流最大流算法是指通过算法设计使得在网络流中的流量达到最大。
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5+100,M = 3E5+100;
const int maxn = 0x7fffff;
struct{
int u,v;
int cap,flow;
int nxt;
}edges[M];
int h[N],idx;
void add(int a,int b,int c)
{
edges[idx].u=a;
edges[idx].v=b;
edges[idx].cap=c;
edges[idx].flow=0;
edges[idx].nxt=h[a];
h[a]=idx++;
}
int n,m,x,S,T;
int dis[N];
bool bfs(int S,int T)
{
memset(dis,-1,sizeof(dis));
queue<int> q;
q.push(S);
dis[S]=0;
while(q.size())
{
int t = q.front();
q.pop();
for(int i=h[t];~i;i=edges[i].nxt)
{
if(dis[edges[i].v]==-1&&edges[i].cap-edges[i].flow>0)
{
dis[edges[i].v]=dis[edges[i].u]+1;
q.push(edges[i].v);
}
}
}
if(dis[T]!=-1)
return 1;
else return 0;
}
int dfs(int cur,int res)
{
if(cur==T||res<=0)
return res;
int flow=0,f;
for(int i = h[cur];~i;i=edges[i].nxt)
{
if(dis[cur]+1==dis[edges[i].v]&&edges[i].cap-edges[i].flow>0)
{
f=dfs(edges[i].v,min(res,edges[i].cap-edges[i].flow));
edges[i].flow+=f;
edges[i^1].flow-=f;
flow+=f;
res-=f;
if(res<=0) break;
}
}
return flow;
}
void dinic(int S,int T)
{
int ansflow = 0;
int cs = 0;
int maxflow=0;
while(bfs(S,T))
{
int p=dfs(S,maxn);
cs++;
ansflow+=p;
maxflow=max(maxflow,p);
}
if(ansflow==0)
printf("Orz Ni Jinan Saint Cow!");
else
{
int num = x/ansflow;
if(x%ansflow!=0) num++;
printf("%d %d",ansflow,num);
}
}
int main()
{
memset(h,-1,sizeof(h));
scanf("%d%d%d",&n,&m,&x);
S=1,T=n;
for(int i=1;i<=m;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);add(b,a,0);
}
dinic(1,n);
return 0;
}