网络流baozi
1.初始网络流,最大流模型p3376P2740
#include <stdio.h> #include <algorithm> #include <cstring> #define INF 100000000 using namespace std; typedef long long ll; const int maxn=2000000; int head[maxn],next[maxn],ver[maxn],tot; int cur[maxn],lev[maxn]; ll wei[maxn];//本题模板提,数据范围要注意一下 int n,m,sou,des; inline void add(int x,int y,int w) { ver[++tot]=y;wei[tot]=w;next[tot]=head[x];head[x]=tot; ver[++tot]=x;wei[tot]=0;next[tot]=head[y];head[y]=tot; } inline ll dinic(int x,ll flow) { if(x==des) return flow; ll v,res=0,delta; for(int i=cur[x];i;i=next[i]) { if(wei[i]>0&&lev[x]<lev[v=ver[i]])//按照这种方法的分层图构建的话,lev[x]<lev[v]就是可以遍历的,否则就会爆炸 { delta=dinic(v,min(wei[i],flow-res)); if(delta) { wei[i]-=delta;wei[i^1]+=delta; res+=delta;if(res==flow) break; } } } if(res!=flow) lev[x]=-1; return res; } inline bool bfs() { static int que[202],ql=1,qr=1; int x,v; for(int i=1;i<=n;i++) lev[i]=-1,cur[i]=head[i]; que[ql]=sou;lev[sou]=0; for(ql=1,qr=1;ql<=qr;ql++) { x=que[ql]; for(int i=head[x];i;i=next[i]) { //printf("%d %d\n",x,ver[i]); if(wei[i]>0&&lev[v=ver[i]]==-1) { lev[v]=lev[x]+1; que[++qr]=v; if(v==des) return true; } } } return false; } inline ll maxflow() { ll ans=0; while(bfs()) ans+=dinic(sou,INF); return ans; } int main() { scanf("%d%d%d%d",&n,&m,&sou,&des); tot=1; for(int i=1;i<=m;i++) { int u,v;ll w; scanf("%d%d%lld",&u,&v,&w); add(u,v,w); } printf("%lld",maxflow()); return 0; }
注意前向弧优化和关于关于分层图的构建
2.费用流(EK算法加上SPFA)
关于SPFA,他死了
#include <stdio.h> #include <algorithm> #include <cstring> #include <queue> using namespace std; const int maxn=100010; int head[maxn],ver[maxn],wei[maxn],flow[maxn],next[maxn],tot; int dis[maxn],vis[maxn],inch[maxn],pre[maxn]; int n,m,s,t; int maxflow=0,mincost=0; inline void add(int x,int y,int fl,int we) { ver[++tot]=y;wei[tot]=we;flow[tot]=fl;next[tot]=head[x];head[x]=tot; } inline bool spfa() { queue<int> q; memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis));//每次记得初始化 q.push(s); dis[s]=0;inch[s]=1<<30;vis[s]=1; while(q.size()) { int x=q.front();q.pop(); vis[x]=0; for(int i=head[x];i;i=next[i]) { if(flow[i]==0) continue; int v=ver[i]; if(dis[x]+wei[i]<dis[v]) { dis[v]=dis[x]+wei[i]; inch[v]=min(inch[x],flow[i]); pre[v]=i;//prei是为了之后的重置而设定的 if(!vis[v]) vis[v]=1,q.push(v); } } } if(dis[t] == 1061109567) return 0;//t没有更新,自然就没有必要继续了 return true; } inline void max_flow() { while(spfa()) { int x=t; maxflow+=inch[t]; mincost+=dis[t]*inch[t]; while(x!=s) { int i=pre[x]; flow[i]-=inch[t]; flow[i^1]+=inch[t];//流量守恒 x=ver[i^1]; } } } int main() { scanf("%d%d%d%d",&n,&m,&s,&t); tot=1;//要使用^1,就一定要从tot=1开始 for(int i=1;i<=m;i++) { int u,v,f,x; scanf("%d%d%d%d",&u,&v,&f,&x); add(u,v,f,x); add(v,u,0,-x); } max_flow(); printf("%d %d\n",maxflow,mincost); return 0; }
https://blog.csdn.net/shanchuan2012/article/details/72417059(关于费用流方法模板的一个总结)
之后我们发现他的时间效率不是非常的理想,所以我们可以进行优化
使用SLF来优化SPFA,并且使用多路增广的方式

spfa的SLF优化就是small label first 优化,当加入一个新点v的时候如果此时的dis[v]比队首dis[q.front()]还要小的话,就把v点加入到队首,否则把他加入到队尾,因为先扩展最小的点可以尽量使程序尽早的结束,一种方法可以用模拟队列,head,tail,但是由于不知道q的数组开多大,所有用双端队列deque<int>q更好些
.https://www.cnblogs.com/victorique/p/8426361.html(优化的集合)
优化过后的SPFA,直接快了将近300ms不止,所以说还是非常牛逼了
但是要注意,因为初始化head[x]=-1,所以说之后要写成i>-1的形式,不然就爆炸了
原理代码:
#include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <iostream> #include <ctime> #include <map> #include <queue> #include <cstdlib> #include <string> #include <climits> #include <set> #include <vector> using namespace std; bool vis[200001];int dist[200001]; //解释一下各数组的含义:vis两个用处:spfa里的访问标记,増广时候的访问标记,dist是每个点的距离标号 int n,m,s,t,ans=0; //s是起点,t是终点,ans是费用答案 int nedge=-1,p[200001],c[200001],cc[200001],nex[200001],head[200001]; //这里是边表,解释一下各数组的含义:p[i]表示以某一点出发的编号为i的边对应点(也就是所谓的ver),c表示编号为i的边的流量,cc表示编号为i的边的费用,nex和head不说了吧。。。 inline void addedge(int x,int y,int z,int zz){ p[++nedge]=y;c[nedge]=z;cc[nedge]=zz;nex[nedge]=head[x];head[x]=nedge; } //建边(数组模拟边表倒挂) inline bool spfa(int s,int t){ memset(vis,0,sizeof vis);//初始化数组 for(int i=0;i<=n;i++)dist[i]=1e9;dist[t]=0;vis[t]=1; //首先SPFA我们维护距离标号的时候要倒着跑,这样可以维护出到终点的最短路径!!!!!! //也就是从终点开始跑啦 deque<int>q;q.push_back(t); //使用了SPFA的SLF优化(SLF可以自行百度或Google) while(!q.empty()){ int now=q.front();q.pop_front(); for(int k=head[now];k>-1;k=nex[k])if(c[k^1]&&dist[p[k]]>dist[now]-cc[k]){//这里因为是反向边,所以是-cc //首先c[k^1]是为什么呢,因为我们要保证正流,但是SPFA是倒着跑的,所以说我们要求c[k]的对应反向边是正的,这样保证走的方向是正确的 dist[p[k]]=dist[now]-cc[k]; //因为已经是倒着的了,我们也可以很清楚明白地知道建边的时候反向边的边权是负的,所以减一下就对了(负负得正) if(!vis[p[k]]){ vis[p[k]]=1; if(!q.empty()&&dist[p[k]]<dist[q.front()])q.push_front(p[k]);else q.push_back(p[k]); //SLF优化 } } vis[now]=0; } return dist[s]<1e9; //判断起点终点是否连通 } inline int dfs(int x,int low){ //这里就是进行増广了 if(x==t){vis[t]=1;return low;} int used=0,a;vis[x]=1; //这边是不是和dinic很像啊 for(int k=head[x];k>-1;k=nex[k])if(!vis[p[k]]&&c[k]&&dist[x]-cc[k]==dist[p[k]]){//这一个要终点理解,因为当初是反向开始跑的,所以现在算多端增广要用dis[x]-cc==dis[y] //这个条件就表示这条边可以进行増广 a=dfs(p[k],min(c[k],low-used));//这玩意儿像极了dinic if(a)ans+=a*cc[k],c[k]-=a,c[k^1]+=a,used+=a; //累加答案,加流等操作都在这了 if(used==low)break; } return used; } inline int costflow(){ int flow=0; while(spfa(s,t)){ //判断起点终点是否连通,不连通说明满流,做完了退出 vis[t]=1; while(vis[t]){ memset(vis,0,sizeof vis); flow+=dfs(s,1e9);//增广就可以从正序开始增广了 //一直増广直到走不到为止(这样也可以省时间哦) } } return flow;//这里返回的是最大流,费用的答案在ans里 } int main() { memset(nex,-1,sizeof nex);memset(head,-1,sizeof head); scanf("%d%d%d%d",&n,&m,&s,&t); for(int i=1;i<=m;i++){ int x,y,z,zz;scanf("%d%d%d%d",&x,&y,&z,&zz); addedge(x,y,z,zz);addedge(y,x,0,-zz); } printf("%d ",costflow());printf("%d",ans); return 0; }
自打代码
#include <stdio.h> #include <algorithm> #include <cstring> #include <queue> using namespace std; const int maxn=200000; int head[maxn],next[maxn],ver[maxn],wei[maxn],flow[maxn],tot=1; int dis[maxn],vis[maxn]; int n,m,s,t,ans=0; inline void add(int x,int y,int fl,int we) { ver[++tot]=y;wei[tot]=we;flow[tot]=fl;next[tot]=head[x];head[x]=tot;//只能有一个++tot } inline bool spfa() { memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); dis[t]=0;vis[t]=1; deque<int> q;q.push_back(t); while(q.size()) { int x=q.front();q.pop_front(); for(int i=head[x];i>-1;i=next[i]) { int v=ver[i]; if(flow[i^1]&&dis[ver[i]]>dis[x]-wei[i]) { dis[ver[i]]=dis[x]-wei[i]; if(!vis[v]) { vis[v]=1;//不长记性 if(!q.empty()&&dis[v]<dis[q.front()]) q.push_front(v);//队列不空才有必要看啊 else q.push_back(v); } } } vis[x]=0; } if(dis[s]>20000000) return false; return true; } inline int dfs(int x,int flow1)//与dinic不同的点在于,这里需要的是用vis数组来存储 { if(x==t) { vis[t]=1;return flow1; } int delta,v,res=0;vis[x]=1; for(int i=head[x];i>-1;i=next[i]) { int v=ver[i]; if(!vis[v]&&dis[v]==dis[x]-wei[i]&&flow[i])//显然这里针对的是vis[v]防止二次增广 { delta=dfs(v,min(flow1-res,flow[i])); if(delta) { ans+=delta*wei[i];//费用流,记得要更新答案 flow[i]-=delta;flow[i^1]+=delta; res+=delta;if(res==flow1) break; } } } return res; } inline int maxflow() { int flow=0; while(spfa()) { vis[t]=1; while(vis[t]) { memset(vis,0,sizeof(vis)); flow+=dfs(s,200000000); } } return flow;//自然是最后返回最大流 } int main() { memset(next,-1,sizeof(next));memset(head,-1,sizeof(head));//初始化以后,i>-1才是终止条件 scanf("%d%d%d%d",&n,&m,&s,&t); for(int i=1;i<=m;i++) { int u,v,f,x; scanf("%d%d%d%d",&u,&v,&f,&x); add(u,v,f,x); add(v,u,0,-x); } printf("%d ",maxflow());printf("%d",ans); return 0; }
总结一下:倒序求SPFA最短路,正序类dinic增广,就可以啦;
再再注意一下,deque的使用,双端队列可太好用了啊
浙公网安备 33010602011771号