推荐一个比较好的博客:https://www.cnblogs.com/SYCstudio/p/7260613.html网络流,这类算法,第一步就是理解概念
1.从概念开始
——百度百科
当你看到这段定义时,会感觉很难理解,有好多术语。
但其中还是有几句有用的。
网络流,就是一个有向图,它的每条边被称为弧。
每条弧有一个容量限制,我们后面再提。
这个算法有什么用呢?
举个例子,我们现在有许多水从总站出发,要运到最后的终点,中间有许多的中转站。
我们可以把水分成许多份,走许多不同的线路到达终点。
但是水在流动时,容量不能超过每根水管的限度,现在问你最多可以运多少水一次性到达终点。
一次性的含义是:水是在同时流动的。
网络流的最大流算法,就是来解决这个问题的。
2.算法思想及实现
网络流中最朴素的算法就是EK算法了。
这里我们要给出许多定义。
1)链
链就相当于有向图中路径的概念,但他们有不同。
有向图中的路径必须是相邻两个节点有正方向的边相连,但链只是一个顶点序列。
2)前向弧和后向弧
沿着链,与链正方向一致的弧称为前向弧,
与链正方向相反的弧称为后向弧
记链为P,则包含前向弧的集合为P+,包含后向弧的集合为P-
3)增广路
P+中的每一条弧均为占满容量上限
P-中的每一条弧流量均不为0
这样的链P称为增广路。
4)残量和残量网络
残量理解为弧中剩余的容量,残量网络就是残量的网络集合。
从残量网络的定义可以看出,原容量网络中的每条弧在残量网络中都化为一条或两条弧。
显然,只要残量网络中存在增广路,流量就可以增大。
可以证明它的逆命题也成立:如果残量网络中不存在增广路,则当前流就是最大流。这就是著名的增广路定理。
由增广路定理可以得出EK算法,它对整张图进行BFS,找到增广路后进行增广,复杂度为O(VE2)
由于EK算法太慢了,Dinic算法就诞生了。
Dinic算法引入了一个叫做分层图的概念。具体就是对于每一个点,我们根据从源点开始的bfs序列,为每一个点分配一个深度,然后我们进行若干遍dfs寻找增广路,每一次由u推出v必须保证v的深度必须是u的深度+1。
Dinic算法还有一个优化,这个优化被称为当前弧优化,即每一次dfs增广时不从第一条边开始,而是用一个数组cur记录点u之前循环到了哪一条边,以此来加速
代码:
// luogu-judger-enable-o2 #include<bits/stdc++.h> #define rd(x) x=read() #define N 100000 #define M 1000000 #define inf INT_MAX using namespace std; int s,t; int cnt; int head[N],next[M<<1],v[M<<1],w[M<<1],depth[N],cur[N]; int n,m; inline int read() { int f=1,x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();} while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} return x*f; } void addEdge(int u1,int v1,int w1){cnt++,next[cnt]=head[u1],v[cnt]=v1,w[cnt]=w1,head[u1]=cnt;} bool bfs() { queue<int>q; while(!q.empty())q.pop(); memset(depth,0,sizeof(depth)); depth[s]=1; q.push(s); while(!q.empty()) { int u=q.front(); q.pop(); for(int i=head[u];i!=-1;i=next[i]) if(w[i]>0&&depth[v[i]]==0) { depth[v[i]]=depth[u]+1; q.push(v[i]); } } if(depth[t])return 1; return 0; }//bfs找增广路 int dfs(int u,int dist) { if(u==t)return dist; for(int & i=cur[u];i!=-1;i=next[i]) //for(int i=head[u];i!=-1;i=next[i]) if(depth[v[i]]==depth[u]+1&&w[i]) { int dis=dfs(v[i],min(dist,w[i])); if(dis>0) { w[i]-=dis,w[i^1]+=dis; return dis; } } return 0; }//dfs增广 int dinic() { int ans=0; while(bfs()) { for(int i=1;i<=n;i++)cur[i]=head[i];//当前弧优化记录cur while(int d=dfs(s,inf))ans+=d; } return ans; } int main() { cnt=-1; memset(head,-1,sizeof(head)); //memset(next,-1,sizeof(next)); rd(n),rd(m),rd(s),rd(t); for(int i=1;i<=m;i++) { int u1,v1,w1; rd(u1),rd(v1),rd(w1); addEdge(u1,v1,w1); addEdge(v1,u1,0); }//加边 int ans=dinic(); printf("%d\n",ans); return 0; }
接下来,我们给每条边增加一个费用,问在最大流情况下的最小费用。
其实操作很简单,就是将Dinic中的BFS改成SPFA就行了。
代码:
// luogu-judger-enable-o2 #pragma GCC optimize(1) #pragma GCC optimize(2) #pragma GCC optimize(3) #include<bits/stdc++.h> #define N 100005 #define rd(x) x=read() using namespace std; int n,m; int s,t; int dis[N],pre[N],last[N],flow[N],vis[N]; int maxflow,mincost; struct Edge{ int to,next,flow,dis; }e[N]; int head[N],cnt; inline int read() { int f=1,x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();} while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} return x*f; } void addEdge(int from,int to,int flow,int dis){e[++cnt].next=head[from],e[cnt].to=to,e[cnt].flow=flow,e[cnt].dis=dis,head[from]=cnt;} bool spfa(int s,int t) { queue<int>q; memset(dis,0x7f,sizeof(dis)); memset(flow,0x7f,sizeof(flow)); memset(vis,0,sizeof(vis)); q.push(s); vis[s]=1,dis[s]=0,pre[t]=-1; while (!q.empty()) { int u=q.front(); q.pop(); vis[u]=0; for(int i=head[u];i!=-1;i=e[i].next) if(e[i].flow>0&&dis[e[i].to]>dis[u]+e[i].dis) { dis[e[i].to]=dis[u]+e[i].dis,pre[e[i].to]=u,last[e[i].to]=i,flow[e[i].to]=min(flow[u],e[i].flow); if(!vis[e[i].to]) { vis[e[i].to]=1; q.push(e[i].to); } } } return pre[t]!=-1; } void MCMF() { while(spfa(s,t)) { int now=t; maxflow+=flow[t]; mincost+=flow[t]*dis[t]; while(now!=s) { e[last[now]].flow-=flow[t]; e[last[now]^1].flow+=flow[t]; now=pre[now]; } } } int main() { cnt=-1; memset(head,-1,sizeof(head)); rd(n),rd(m),rd(s),rd(t); for(int i=1;i<=m;i++) { int u,v,w,f; rd(u),rd(v),rd(w),rd(f); addEdge(u,v,w,f),addEdge(v,u,0,-f); } MCMF(); printf("%d %d\n",maxflow,mincost); return 0; }