网络流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;
}
View Code

注意前向弧优化和关于关于分层图的构建

 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;
} 
View Code

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;
}
View Code

自打代码

#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;
}
View Code

总结一下:倒序求SPFA最短路,正序类dinic增广,就可以啦;

再再注意一下,deque的使用,双端队列可太好用了啊

posted @ 2020-09-05 08:30  ILH  阅读(175)  评论(0)    收藏  举报