网络流学习笔记

最大流

dinic算法:每次增广出长度一样的若干条路径,用朴素的bfs和dfs实现。

费用流

EK算法:每次找出一条费用最小的流量跑走,这样可以保证最后一定是最大流且此时费用最小。

zkw费用流:不会,留坑代填

最小割:给定一张图,问使源点和汇点不连通的最小代价,可以证明这是最大流。

最小割树:一种神奇的树,构建方法如下,1、从当前图中随便找出两个点,跑一边最小割求出此时两个点之间的最小割,在这两个点间连边,然后我们把满流的边删掉,这样会把图分成好几个块,我们令s所在块为一块,剩下的点为一块,在分治下去,这样我们一共要跑n-1次最小割,连出来的一定是一颗树。

有一个结论,任意两点之间的最小割是最小割树上两点路径上边权最小值。为什么,考虑我们把图分为两块的时候,当前的最小割就是我们把图分为两块的最小割,也就是从两块中任意拿出两个点,它们之间的最小割的上界。

二元关系最小割

有这样的一个问题,有多组二元关系,形如xy都选的价值,选一个的价值,都不选的价值,可以建出这么一张图。

然后我们可以根据所得的信息求出每条边的流量,跑最小割就可以了。

最大权闭合子图

有这样一个问题,给定一个DAG,求一个最大权子图,满足这个子图里的所有元素连出去的边指向的点也都在这个子图里。

我们可以贪心的先把所有正权点都选上 ,但这样可能不满足它是闭合的, 

所以我们从源点向每个正权点连流量为点权的边,所有负权点向汇点连流量为点权的绝对值的边。

相当于是说,对于一些不合法的正负点权来说,要么就花正点权的代价不要正点,要么就把负点权选上。

这张图的最大流,也就是最小割,就是我们的最小代价。

bzoj4873寿司餐厅:mx^2+cx表示选择一种代号就会产生固定的mx^2代价,每选择一种寿司就会增加x的代价,考虑如下限制,区间lr被选择时,区间[l+1,r]区间[l,r-1]也必须被选择,这样的话闭合子图的模型就出来了,对于每种代号新开一个点,所有长度为1的区间向所对应的代号连边,表示选了这个区间,这种代号产生的代价就必须产生,我们发现答案就是这张图上的最大权闭合子图。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define N 12009
#define xi 102 
#define M 1000002
#define inf 2e9
using namespace std;
typedef long long ll;
queue<int>q;
int head[N],cur[N],deep[N],tot=1,top,n,m,a[N],b[N],val[N],id[xi][xi],c[N],num;
ll ans=0;
inline int rd(){
    int x=0;char c=getchar();bool f=0;
    while(!isdigit(c)){if(c=='-')f=1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}
struct edge{
    int n,to,l;
}e[M];
inline void add(int u,int v,int l){
    e[++tot].n=head[u];e[tot].to=v;head[u]=tot;e[tot].l=l;
    e[++tot].n=head[v];e[tot].to=u;head[v]=tot;e[tot].l=0;
}
inline bool bfs(int s,int t){
    memset(deep,0,sizeof(deep));
    memcpy(cur,head,sizeof(head));
    q.push(s);deep[s]=1;
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int i=head[u];i;i=e[i].n){
            int v=e[i].to;
            if(!deep[v]&&e[i].l){
                deep[v]=deep[u]+1;
                q.push(v);
            }
        }
    }
    return deep[t];
}
int dfs(int u,int t,int l){
    if(!l||u==t)return l;
    int flow=0,f;
    for(int &i=cur[u];i;i=e[i].n){               //!!!
        int v=e[i].to;
        if(deep[v]==deep[u]+1&&(f=dfs(v,t,min(l,e[i].l)))){
            e[i].l-=f;e[i^1].l+=f;l-=f;flow+=f;
            if(!l)break;
        }
    }
    return flow;
}
int main(){
    n=rd();m=rd(); 
    for(int i=1;i<=n;++i)a[i]=rd(),b[i]=a[i];
    sort(b+1,b+n+1);
    top=unique(b+1,b+n+1)-b-1;
    for(int i=1;i<=n;++i)c[i]=lower_bound(b+1,b+top+1,a[i])-b;
    for(int i=1;i<=n;++i)
      for(int j=i;j<=n;++j){
        id[i][j]=++num;val[num]=rd();
        if(id[i-1][j])add(id[i-1][j],id[i][j],inf);
        if(id[i][j-1])add(id[i][j],id[i][j-1],inf);
      }    
    int T=num+top;
    for(int i=1;i<=n;++i)val[id[i][i]]-=a[i],add(id[i][i],num+c[i],inf),val[num+c[i]]=-m*a[i]*a[i];
    for(int i=1;i<=T;++i){
        if(val[i]>0)add(0,i,val[i]),ans+=val[i];
        else add(i,T+1,-val[i]);
    }
    while(bfs(0,T+1))ans-=dfs(0,T+1,inf);
    printf("%lld
",ans); 
    return 0;
} 
View Code

有上下界的网络流

有上下界的网络流可以用来解决每条边的流量是一个区间的问题。

无源汇有上下界的可行流(环流):我们可以先让每条边都跑上流量的下界,但这样流量有可能不守恒,所以我们新建源点和汇点,令每个点当前流入量和流出量差的绝对值为x。

如果流入量大于流出量,我们就从源点向该点连流量为x的边,相当于让这个点流出x的流量,反之我们从该点向汇点连流量为x的边,相当于让这个点再流进x的流量。

那么我们新加入的每条边都导入/导出了我们一开始直接跑掉流量下界时造成的流入量和流出量的差。

跑完最大流之后检查一下后加入的边是否满流,如果满流,说明每条边都满足了要求。

有源汇有上下界的可行流:这个和上面的差别就是有了源汇,其实我们可以通过转换变成上面的问题,判断流了多少流量,只需要判断t-s边的流量就好了。

有源汇有上下界的最大流:先跑可行流,此时下界已经满足合法,我们可以把超级源和超级汇去掉,在残量网络上跑最大流就可以了,最终的最大流流量=可行流流量+新增广出的流量,其实这个可行流量在我们第二次最大流的时候就自动加上了,其实就是反边的流量。

有源汇有上下界的最小流:这个比较难理解,我们先不加t-s的边,然后跑一个可行流,再把t-s边加上,再跑一个可行流,那么t-s的流量就是最小流,因为我们要满足流量下限的限制,所以这一步可以尽量填充较多的流量,减小第二次产生的流量。

bzoj4464[Jsoi2013]旅行时的困惑:裸的树上最小路径覆盖模型,可以用费用流做,甚至可以用树形dp做,用最小流的话图上边设为(1,inf),s到i设为(0,inf),i到t设为(0,inf),跑有源汇最小流。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define N 101002
#define inf 2e9
using namespace std;
queue<int>q;
int head[N],tot=1,deep[N],cur[N],n,du[N],ans;
inline int rd(){
    int x=0;char c=getchar();bool f=0;
    while(!isdigit(c)){if(c=='-')f=1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}
struct edge{int n,to,l,f;}e[N<<4];
inline void add(int u,int v,int l){
    e[++tot].n=head[u];e[tot].to=v;head[u]=tot;e[tot].l=l;
    e[++tot].n=head[v];e[tot].to=u;head[v]=tot;e[tot].l=0;
}
inline bool bfs(int s,int t){
    memset(deep,0,sizeof(deep));
    memcpy(cur,head,sizeof(cur));
    q.push(s);deep[s]=1;
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int i=head[u];i;i=e[i].n){
            int v=e[i].to;
            if(!deep[v]&&e[i].l){deep[v]=deep[u]+1;q.push(v);}
        }
    }
    return deep[t];
}
int dfs(int u,int t,int l){
    if(!l||u==t)return l;
    int flow=0,f;
    for(int &i=cur[u];i;i=e[i].n){
        int v=e[i].to;
        if(deep[v]==deep[u]+1&&(f=dfs(v,t,min(l,e[i].l)))){
            e[i].l-=f;e[i^1].l+=f;flow+=f;l-=f;
            if(!l)break;
        }
    }
    return flow;
}
inline int dinic(int s,int t){
    int ans=0;
    while(bfs(s,t))while(int f=dfs(s,t,inf))ans+=f;
    return ans;
}
int main(){
    n=rd();int x,y;
    for(int i=1;i<n;++i){
        x=rd();y=rd();x++;y++;
        add(y,x,inf);
        du[x]++;du[y]--;
    }
    for(int i=1;i<=n;++i)add(0,i,inf),add(i,n+1,inf);
    for(int i=1;i<=n;++i)
        if(du[i]<0)add(i,n+3,-du[i]);else add(n+2,i,du[i]);
    dinic(n+2,n+3);
    add(n+1,0,inf);
    cout<<dinic(n+2,n+3); 
    return 0;
}
View Code

未完待续。。。

原文地址:https://www.cnblogs.com/ZH-comld/p/10155707.html