北京培训记day3
网络流
一、基础知识点:
【容量网络】
图G(V,E)为有向网络,在V中指定一个源点和一个汇点,流量从源点出发经过有向网络流向汇点。对于每一条有向边有权值C,称作弧的容量。有向边称为弧。这样的有向网络称为容量网络。
【弧的流量】
容量网络G中的每条弧上的实际流量称作弧的流量。
【网络流】
有向图G中弧上流量的集合,称为网络流。
【可行流】
在容量网络中满足下列条件的网络流称为可行流。
1、弧流量限制:流量要大于等于0且小于等于弧的容量。
2、平衡条件:流量只能从源点流出经过容量网络在汇点会聚。在网络中不会凭空出现和消失。
【可行流流量】
源点的净流出流量或汇点的净流入流量。
【零流】
流量为零。
【伪流】
满足限制条件但不满足平衡条件的网络流。也称为容量可行流。
【最大流】
在容量网络中,满足弧流量限制条件和平衡条件的最大可行流,称为网络最大流,简称最大流。
【饱和弧与非饱和弧】
在容量网络中的某一条弧当流量等于容量时,此弧为饱和弧,否则为非饱和弧。
【零流弧和非零流弧】
在容量网络中的某一条弧当流量等于零时,此弧为零流弧,否则为非零流弧。
【链】
在容量网络中,顶点序列(U1,U2……Un,V)为一条链,要求两个相连的点之间有一条弧。
注意:链和有向图中有向路径不是相同概念,有向路径中有向边的方向是相同的,但链这里不要求这样。
【正方向】
设P为容量网络中源点到汇点的一条链,由源点到汇点的方向就为正方向。
【前向弧与后向弧】
方向与链的正方向相同的弧称为正向弧。反之称为后向弧。
建法:建一条容量为f的正向弧,以及一条容量为0的后向弧,运行时上减下加。
P.S.若为双向边,则建一条容量为f的正向弧和一条容量为f的反向弧。
【增广路】
在容量网络中P是从源点到汇点的一条链,如果:
1、P的所有前向弧都为非饱和弧。
2、P的所有后向弧都为非零流弧。
则称P是一条增广路(增广链或可改进路)。
沿着增广路改进可行流的操作称为增广。
【残留容量】
对于给定的容量网络中某条弧的容量为F,流量为C,残留容量就位F-C。
【残留网络】
以残留容量建成的容量网络。也称为剩余网络。
【割】
在容量网络G(V,E)中存在E'为E的子集。若G在删掉E'的所有边后不连通,E'就为G的割。因为割将G的顶点集划分为S,T两个集合。所以记割为(S,T)。若源点s在点集S中,汇点t在点集T中,则称该割为S-t割。
【割的容量】
割的所有前向弧(定义源点到汇点为正方向)的容量只和。
【最小割】
割的容量最小的割称为最小割。
【割的净流量】
割中所有弧的流量之和,前向弧流量为正值,后向弧的流量为负值。
【增广路定理】
若容量网络中可行流f已经为最大流的充要条件是在容量网络中不存在增广路。
【最大流最小割定理】
对于容量网络G,其最大流量等于最小割流量。
二、dinic
计算残余网络的层次图,定义h[i]表示点i到S的最少边数,按h[i]分层,只保留不同层之间的边
在层次图中利用dfs增广直到不存在增广路
重复以上步骤,复杂度:O(m*n*n)
#include<cstdio> #include<cstring> #include<iostream> #include<string> #include<algorithm> #include<map> #include<vector> using namespace std; const int N=1100; const int INF=0x3f3f3f3f; struct Node { int to,cap,rev; }; vector<Node> v[N]; bool used[N]; void add(int from,int to,int cap) { Node k1,k2; k1.to=to;k1.cap=cap;k1.rev=v[to].size(); k2.to=from;k2.cap=0;k2.rev=v[from].size()-1; v[from].push_back(k1); v[to].push_back(k2); } int dfs(int s,int t,int f) { if(s==t) return f; used[s]=true; for(int i=0,siz=v[s].size();i<siz;i++) { Node &tmp = v[s][i]; if(used[tmp.to]==false && tmp.cap>0) { int d=dfs(tmp.to,t,min(f,tmp.cap)); if(d>0) { tmp.cap-=d; v[tmp.to][tmp.rev].cap+=d; return d; } } } return 0; } int max_flow(int s,int t) { int flow=0; while(1) { memset(used,false,sizeof(used)); int f=dfs(s,t,INF); if(f==0) return flow; flow+=f; } } int main() { int n,m; cin>>n>>m; for(int i=0;i<n;i++) { int x,y,z; cin>>x>>y>>z; add(x,y,z); } cout<<max_flow(1,m)<<endl; }
三、EK(比较慢)
最小费用最大流
把dinic中的dfs换成spfa,图不分层即可
每次找最短的增广路,最终答案即为最小费用最大流
//代码未测试,可能是错的QAQ #include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #include<queue> #define inf 1073741823 using namespace std; const int Mx1=2010; const int Mx2=40010; struct Node { int to,val,cst,nxt; } str[Mx2]; int n,m,fr,to,cost,flow,cnt,head[Mx1],pre[Mx1],edge[Mx1],dis[Mx1]; void insert(int u,int v,int val,int cst) { str[cnt].to=v; str[cnt].val=val; str[cnt].cst=cst; str[cnt].nxt=head[u]; head[u]=cnt++; str[cnt].to=u; str[cnt].val=0; str[cnt].cst=-cst; str[cnt].nxt=head[v]; head[v]=cnt++; } bool Spfa(int s,int t) { queue <int> q; memset(pre,-1,sizeof(pre)); memset(dis,0x3F,sizeof(dis)); dis[s]=0; pre[s]=0; q.push(s); while (!q.empty()) { int u=q.front();q.pop(); for(int e=head[u];e!=-1;e=-str[e].nxt) { int v=str[e].to; if(str[e].val>0&&dis[u]+str[e].cst<dis[v]) { dis[v]=dis[u]+str[e].cst; pre[v]=u; edge[v]=e; q.push(v); } } } if(pre[t]==-1) return false; return true; } void solve(int s,int t) { while(Spfa(s,t)) { int f=inf; for(int u=t;u!=s;u=pre[u]) if(str[edge[u]].val<f) f=str[edge[u]].val; flow+=f; cost+=dis[t]*f; for(int u=t;u!=s;u=pre[u])//调整容量 str[edge[u]].val-=f,str[edge[u]^1].val+=f; } printf("%d\n",cost); } void build() { memset(head,-1,sizeof(head)); //balabala..... } int main() { scanf("%d%d%d%d",&n,&m,&fr,&to); build(); solve(fr,to); return 0; }
三、有上下界的网络流
要消除下界,也就是把下界化为0,则要先将下界的流量统计上
若无源汇则不用加S-T的边,有源汇只能在DAG上跑
上图直接从超级源点向超级汇点跑最大流(最小费用最大流)即可得到一组可行流
删掉原来汇点到源点之间的边
在残余网络上从原来的源点向汇点跑一遍最大流,与可行流相加即为最大流
在残余网络上从原来的汇点向源点跑一边最大流,用可行流减去即为最小流