网络流 && 洛谷 P3376 【模板】网络最大流 && P3381 【模板】最小费用最大流
最大流传送门
费用流传送门
网络流
很形象的定义:城市水管。
城市之间有许多水管,每根水管有一个最大容量,有一个源点(即出水点)s,和一个汇点t(回收水点)。
而最大流就是求最多能有多少水从源点流向汇点。
用找增广路的思想,每次不断找到增广路,更新答案。
注意要加反向边(返回操作),保证正确性。
但这样明显效率很低,所以我们引进dinic算法——一次找多条增广路。
对于每个点,遍历每一个其连向的点,不断dfs,直到到达汇点,然后回溯、统计。
看起来没问题,但实际上仍会出现找一遍增广路能跑完整个图甚至重复跑到同一个点的情况,怎么处理呢?
我们可以按照残量网络构建分层图。
每次走到下一层的节点即可。
这样能保证正确性吗?不会有同层之间流的情况吗?
答案是正确的。
因为虽然这次不能走同一层,但只要有残量,总会有一次分层,使得连向的点=这个点层数+1中。
有一种优化叫做当前弧优化:在某一次分层中,对于某一个点,若已经dfs到了第i个儿子,那么前i-1个儿子一定都流了最大值了,所以可以记录下这个i,下次走到这个节点时,可直接从i开始搜。
再来说费用流。
形象的说就是每条水管运输费用不同。
也就是边上多了一个权值,表示流单位流量需要消耗的费用。
费用流就是求在保证最大流的情况下,费用最少是多少。
怎么求呢?
一样的增广路思想。
每次找到一条s到t边权和最小的路(即为最短路),然后计算这条路的最大流量(即为流量最小的那条边的流量)。
每找到这样一条路,就更新一下答案和相应边的信息。
而这个求最短路的过程可以用spfa实现。(因为有负边权)
感觉比最大流更好理解些。
AC代码
#include<iostream> #include<algorithm> #include<cmath> #include<cstdio> #include<cstring> using namespace std; int n,m,s,t,p[201],cnt=-1,dis[205],vis[205],cur[205]; struct node{ int v,cap,flow,next; }e[10005]; void insert(int u,int v,int cap,int flow){ cnt++; e[cnt].v=v; e[cnt].cap=cap; e[cnt].flow=flow; e[cnt].next=p[u]; p[u]=cnt; } bool bfs(){ queue<int> q; memset(dis,-1,sizeof(dis)); q.push(s); dis[s]=0; while(!q.empty()){ int u=q.front(); q.pop(); for(int i=p[u];i!=-1;i=e[i].next){ int v=e[i].v; if(dis[v]==-1&&e[i].cap>e[i].flow){ dis[v]=dis[u]+1; q.push(v); } } } if(dis[t]==-1) return false; else return true; } long long dfs(int u,int maxflow){ if(u==t||maxflow==0) return maxflow; long long flow=0; for(int &i=cur[u];i!=-1;i=e[i].next){ int v=e[i].v; if(dis[v]==dis[u]+1){ long long f=dfs(v,min(maxflow,e[i].cap-e[i].flow)); e[i].flow+=f; e[i^1].flow-=f; flow+=f; maxflow-=f; if(maxflow==0) break; } } return flow; } long long dinic(){ long long fl=0; while(bfs()){ for(int i=1;i<=n;i++) cur[i]=p[i]; fl+=dfs(s,0x3f3f3f3f); } return fl; } int main() { memset(p,-1,sizeof(p)); cin>>n>>m>>s>>t; for(int i=1;i<=m;i++){ int u,v,w; scanf("%d%d%d",&u,&v,&w); insert(u,v,w,0); insert(v,u,0,0); } cout<<dinic(); return 0; }
#include<iostream> #include<algorithm> #include<cmath> #include<cstdio> #include<cstring> #include<queue> using namespace std; const int maxn=5005; int n,m,s,t,cnt=1,p[maxn],dis[maxn],flow[maxn],vis[maxn],ansflow,anscost,pre[maxn],last[maxn]; struct node{ int v,next,value,cap,flow; }e[maxn*20]; void insert(int u,int v,int cap,int flow,int value){ cnt++; e[cnt].v=v; e[cnt].next=p[u]; e[cnt].cap=cap; e[cnt].flow=flow; e[cnt].value=value; p[u]=cnt; } queue<int> q; bool spfa(){ memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); memset(flow,0,sizeof(flow)); flow[s]=0x3f3f3f3f; dis[s]=0; q.push(s); while(!q.empty()){ int u=q.front(); q.pop(); vis[u]=0; for(int i=p[u];i!=-1;i=e[i].next){ int v=e[i].v; if(e[i].cap>e[i].flow&&(dis[u]+e[i].value<dis[v])){ dis[v]=dis[u]+e[i].value; pre[v]=u; last[v]=i; flow[v]=min(e[i].cap-e[i].flow,flow[u]); if(!vis[v]){ vis[v]=1; q.push(v); } } } } return dis[t]!=0x3f3f3f3f; } void MFMC(){ while(spfa()){ ansflow+=flow[t]; anscost+=flow[t]*dis[t]; int now=t; while(now!=s){ e[last[now]].flow+=flow[t]; e[last[now]^1].flow-=flow[t]; now=pre[now]; } } } int main() { memset(p,-1,sizeof(p)); cin>>n>>m>>s>>t; for(int i=1;i<=m;i++){ int u,v,f,w; scanf("%d%d%d%d",&u,&v,&f,&w); insert(u,v,f,0,w); insert(v,u,0,0,-w); } MFMC(); printf("%d %d",ansflow,anscost); return 0; }