最大流
我再次博客上暂不叙述预留推进(SAP),事实是,我不会,暂且搁置,以后会补充
最大流在实际问题中的应用是,你要从一个源点s送水至汇点t,这些点中有水管链接,水管的最大能通过的水有不同,让你求单位时间内你最多能送多少水。
我们的流网络可以理解为一群有向边图
一些想法
我们定义\(f(u,v)\)为u节点到v节点的流量。,\(c(u,v)\)为u节点到v节点的流量限制
首先我们由于不能爆水管,有这个显而易见的式子
\[
0 \le f(u,v) \le c(u,v)
\]
其次我们来,思考一下对于每个节点的的流出和流入,显然的我们可以把KCL(即Kirchhoff's Current Law),推广到oi界上,我们有
\[
\sum _{v\in V} f(v,u)=\sum_{v \in V} f(u,v)
\]
想法结束进入正题
经过度娘的帮助,我们知道一种叫做增广路的东西,在一个残余网络(就是说某些管子已经有水了),就是说从源节点到汇节点的一条能走水的路径
这样我们知道,假如,我们一遍又一遍的操作之后,没有增广路可以找了,那么我们必定求出了最大流
然而怎么寻找增广路,这是一个问题。
经过思考之后我们可以用一个十分暴力的思想来解决,进行深搜,主要思想是
- 我们可以每一次从原点开始深搜
- 搜到汇节点,返回增广路上的最小权值、
- 重复第二步,知道找不到增广路
我们的具体实现,建反向边,把正的流顶回去的操作
具体实现
#include <iostream> #include <cstdio> #include <cstring> #include <queue> using namespace std; const int Maxn=201; int n,m,min1,s,ans,t,u,v,w,Map[Maxn][Maxn],pre[Maxn]; bool flag[Maxn]; void dfs(int u){ flag[u]=1; if(u==t) return ; for(int i=1;i<=n;i++){ if(!flag[i]&&Map[u][i]>0){ pre[i]=u; dfs(i); if(flag[t]){ min1=min(min1,Map[u][i]); return ; } } } return ; } int main(){ // freopen("flow.in","r",stdin); scanf("%d%d%d%d",&n,&m,&s,&t); for(int i=1;i<=m;i++){ scanf("%d%d%d",&u,&v,&w); Map[u][v]+=w; } while(1){ // printf("9999:\n"); memset(pre,0,sizeof pre); memset(flag,0,sizeof flag); min1=0x3f3f3f3f; dfs(s); if(!flag[t]) break; int now=t; while(1){ if(now==s) break; Map[pre[now]][now]-=min1; Map[now][pre[now]]+=min1; now=pre[now]; } } printf("%d",ans); return 0; }
值得一提的有两点
- 如果你拿矩阵存,要对重边处理
- dfs 的类型。。。
有兴趣去翻翻历史,EK和Dinic的渊源
好了我们开始有一点DINIC的雏形了,dinic的优秀是在,他做了一个叫层次网络的东西,可以大大加快速度
我们来说说Dinic算法的套路
- 对不饱和边进行广搜
- 搜到之后 按照上面的套路做深搜,要加一下是否满足层次网络
- 计算答案
- 返回1步,能搜到t则继续,否则退出循环
而且有两个简单优化
- 当前弧
- 满流 ,好像是
我上代码了
#include <iostream> #include <cstdio> #include <cstring> #include <queue> using namespace std; const int Maxn=2*1e4+11,Maxm=2*1e5+11; struct Edge{ int fr,to,lac,wg; }edge[Maxm]; int s,t,n,m,u,v,w,cnt,h[Maxn],dep[Maxn],ans,cur[Maxn]; bool flag[Maxn],vis[Maxn]; int read(){ int x=0; char ch=getchar(); while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch-'0'); ch=getchar(); } return x; } void insert(int u,int v,int w){ edge[cnt].to=v; edge[cnt].lac=h[u]; edge[cnt].fr=u; edge[cnt].wg=w; h[u]=cnt++; } void bfs(int u){ queue<int>q1; q1.push(u); dep[u]=0,flag[u]=1; while(!q1.empty()){ int fr=q1.front(); q1.pop(); for(int i=h[fr];i!=-1;i=edge[i].lac){ int to=edge[i].to; if((!edge[i].wg)||flag[to]) continue; q1.push(to); dep[to]=dep[fr]+1; flag[to]=1; } } } int dfs(int u,int min1){ if(u==t) return min1; int sum=min1; flag[u]=1; for(int i=cur[u];i!=-1;i=edge[i].lac){ int to=edge[i].to; if(edge[i].wg==0) continue; if(dep[to]!=dep[u]+1) continue; if(flag[to]) continue; cur[u]=i;//当前弧 int ret1=dfs(to,min(sum,edge[i].wg)); edge[i].wg-=ret1,edge[i^1].wg+=ret1; sum-=ret1; if(!sum) break;//满流 } return min1-sum; } int main(){ // freopen("Dinic.in","r",stdin); n=read(),m=read(),s=read(),t=read(); memset(h,-1,sizeof h); for(int i=1;i<=m;i++){ u=read(),v=read(),w=read(); insert(u,v,w); insert(v,u,0); } while(1){ memset(dep,-1,sizeof dep); memset(flag,0,sizeof flag); memcpy(cur,h,sizeof cur); bfs(s); if(dep[t]==-1) break; memset(flag,0,sizeof flag); int ret=dfs(s,0x3f3f3f3f); ans+=ret; } printf("%d",ans); return 0; }
值得一提的是 用^,天下我有,
然后优化,一定要加,前人的经验呀,,,血与泪的教训
最小费用最大流
SFPA
把深搜和广搜,改成某个知名的的单源最短路求法就好了 ,慎用,f是经验
#include <iostream> #include <cstdio> #include <cstring> #include <queue> using namespace std; const int Maxm=150001,Maxn=5005; struct Edge{ int to,flo,co,lac;//co指cost ,flo 指 flow }edge[Maxm]; int n,m,h[Maxn],s,t,x,y,z,f,cnt,co[Maxn],flow[Maxn],pre[Maxn],maxflo,minco,last[Maxn]; bool vis[Maxn]; void insert(int x,int y,int z,int f){ edge[cnt].to=y; edge[cnt].lac=h[x]; edge[cnt].flo=z; edge[cnt].co=f; h[x]=cnt++; } bool SPFA(int s,int t){ memset(flow,0x3f3f3f3f,sizeof flow); pre[t]=-1; memset(co,0x3f3f3f3f,sizeof co); memset(vis,0,sizeof vis); queue<int> q; q.push(s);vis[s]=1;co[s]=0,pre[s]=-1; while(!q.empty()){ int fr=q.front(); q.pop(); vis[fr]=0; for(int i=h[fr];i!=-1;i=edge[i].lac){ int to=edge[i].to; if(edge[i].flo>0&&co[to]>co[fr]+edge[i].co){ co[to]=co[fr]+edge[i].co; pre[to]=fr; last[to]=i; flow[to]=min(flow[fr],edge[i].flo); if(!vis[to]){ q.push(to); vis[to]=1; } } } } return pre[t]!=-1; } void SFPA(){ while(SPFA(s,t)){ int now=t; maxflo+=flow[t]; minco+=flow[t]*co[t]; while (now!=s){ edge[last[now]].flo-=flow[t]; edge[last[now]^1].flo+=flow[t]; now=pre[now]; } } } int main() { // freopen("SFPA.in","r",stdin); memset(h,-1,sizeof h); scanf("%d%d%d%d",&n,&m,&s,&t); for(int i=1;i<=m;i++){ scanf("%d%d%d%d",&x,&y,&z,&f); insert(x,y,z,f); insert(y,x,0,-f); } SFPA(); printf("%d %d",maxflo,minco); return 0; }
这个算法是用了广搜的思想,Dinic的骨架,其正确性来源于流量优先,而费用次之
注意记录前驱
咕,以后还会增加内容的,我累了。。
记于2020年2月1日
来源:https://www.cnblogs.com/zhltao/p/12250784.html