网络流

网络流=带反悔的贪心。——517

个人认为网络流=最大流dinic/费用流板子+玄学意会建图。

网络流朴素算法ek

对于每条边 \((u,v,w)\) ,建一条相应的反向边 \((v,u,0)\)
算法执行时,先从源点s bfs,看看到t最多能流多少,对于每个节点记录它的前驱节点,如果到t的流量不为0,那么从t回溯回s,将每条边的容量减去流量,其反向边的容量加上流量,然后把答案加上所有回溯到的边的流量;否则停止执行,返回结果。最坏复杂度 \(O(nm^2)\)

#include<bits/stdc++.h>
#define maxn 10005
#define maxm 100005
#define INF 1050000000
using namespace std;
template<typename tp>
void read(tp& x){
    x=0;
    char c=getchar();
    bool sgn=0;
    while((c<'0'||c>'9')&&c!='-')c=getchar();
    if(c=='-')sgn=1,c=getchar();
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
    if(sgn)x=-x;
}
template<typename tp>
void write(tp x){
    if(x<0)putchar('-'),write(-x);
    else{
        if(x>=10)write(x/10);
        putchar(x%10+'0');
    }
}
struct edge{
    int to,next,w;
}e[maxm<<1];
int head[maxn],cnte;
void add(int u,int v,int w){
    e[++cnte].to=v;
    e[cnte].w=w;
    e[cnte].next=head[u];
    head[u]=cnte;
}
int n,m,s,t,pre[maxn],edg[maxn],flow[maxn];
bool bfs(){
    for(int i=1;i<=n;i++)pre[i]=edg[i]=-1,flow[i]=INF;
    pre[s]=0;
    queue<int> q;
    q.push(s);
    while(!q.empty()){
        int u=q.front();
        q.pop();
        if(u==t)break;
        for(int i=head[u];~i;i=e[i].next){
            int v=e[i].to;
            if(e[i].w>0&&pre[v]==-1){
                pre[v]=u;
                edg[v]=i;
                flow[v]=min(flow[u],e[i].w);
                q.push(v);
            }
        }
    }
    return pre[t]!=-1;
}
int ek(){
    int ans=0;
    while(bfs()){
        int x=t;
        while(x!=s){
            e[edg[x]].w-=flow[t];
            e[edg[x]^1].w+=flow[t];
            x=pre[x];
        }
        ans+=flow[t];
    }
    return ans;
}
signed main(){
    read(n),read(m),read(s),read(t);
    for(int i=1;i<=n;i++)head[i]=-1;
    cnte=-1;
    for(int i=1;i<=m;i++){
        int u,v,w;
        read(u);read(v);read(w);
        add(u,v,w);
        add(v,u,0);
    }
    write(ek());
    return 0;
}

网络流进阶算法dinic

解释不来,请上网搜索……
最坏复杂度 \(O(n^2m)\)
head1[]:当前弧优化,极小部分题会卡,如LOJ117,加了快10倍

#include<bits/stdc++.h>
using namespace std;
const int maxn=10003,maxm=200003,INF=1050000000;
struct edge{int to,next,w;}e[maxm<<1];
int head[maxn],head1[maxn],cnte;
void add(int u,int v,int w){e[++cnte].to=v,e[cnte].w=w,e[cnte].next=head[u],head[u]=cnte;}
void addedge(int u,int v,int w){add(u,v,w),add(v,u,0);}
int n,s,t,dep[maxn],q[maxn];
bool bfs(){
    for(int i=1;i<=n;i++)dep[i]=0,head1[i]=head[i];
    dep[s]=1;
    int *qhead=q,*qtail=q;
    *qtail++=s;
    while(qhead!=qtail){
        int u=*qhead++;
        for(int i=head[u];~i;i=e[i].next){
            int v=e[i].to;
            if(e[i].w&&dep[v]==0){
                dep[v]=dep[u]+1;
                *qtail++=v;
            }
        }
    }
    return dep[t]!=0;
}
int dfs(int u,int low){
    if(low==0||u==t)return low;
    int flow=0;
    for(int &i=head1[u];~i;i=e[i].next){
        int v=e[i].to;
        if(e[i].w&&dep[v]==dep[u]+1){
            int tmp=dfs(v,min(low,e[i].w));
            if(tmp==0)dep[v]=0;
            else{
                flow+=tmp;
                low-=tmp;
                e[i].w-=tmp;
                e[i^1].w+=tmp;
                if(low==0)break;
            }
        }
    }
    return flow;
}
int dinic(){
    int ans=0;
    while(bfs())ans+=dfs(s,INF);
    return ans;
}
int main(){
    int m;
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(int i=1;i<=n;i++)head[i]=-1;
    cnte=-1;
    while(m--){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        addedge(u,v,w);
    }
    printf("%d",dinic());
    return 0;
}

费用流朴素算法spfa+ek

对于每条边 \((u,v,w,cost)\) ,建反向边 \((v,u,w,-cost)\)
该算法即在最大流ek的基础上把bfs改为spfa。为什么不能直接用dijkstra?因为有负环。

#include<bits/stdc++.h>
#define maxn 5005
#define maxm 100005
#define INF 1050000000
using namespace std;
struct edge{int to,next,w,cost;}e[maxm<<1];
int head[maxn],cnte;
void add(int u,int v,int w,int cost){e[++cnte].to=v,e[cnte].w=w,e[cnte].cost=cost,e[cnte].next=head[u],head[u]=cnte;}
void addedge(int u,int v,int w,int cost){add(u,v,w,cost),add(v,u,w,-cost);}
int n,s,t,pre[maxn],edg[maxn],flow[maxn],maxflow,dis[maxn],mincost;
bool vis[maxn];
bool spfa(){
    for(int i=1;i<=n;i++)flow[i]=dis[i]=INF,vis[i]=0;
    pre[t]=-1;
    dis[s]=0;
    queue<int> q;
    q.push(s);
    while(!q.empty()){
        int u=q.front();
        q.pop();
        vis[u]=0;
        for(int i=head[u];~i;i=e[i].next){
            int v=e[i].to;
            if(e[i].w>0&&dis[u]+e[i].cost<dis[v]){
                dis[v]=dis[u]+e[i].cost;
                pre[v]=u;
                edg[v]=i;
                flow[v]=min(flow[u],e[i].w);
                if(!vis[v]){
                    vis[v]=1;
                    q.push(v);
                }
            }
        }
    }
    return pre[t]!=-1;
}
void micmxf(){
    while(spfa()){
        int x=t;
        while(x!=s){
            e[edg[x]].w-=flow[t];
            e[edg[x]^1].w+=flow[t];
            x=pre[x];
        }
        maxflow+=flow[t];
        mincost+=flow[t]*dis[t];
    }
}
signed main(){
    int m;
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(int i=0;i<=n;i++)head[i]=-1;
    cnte=-1;
    for(int i=1;i<=m;i++){
        int u,v,w,cost;
        scanf("%d%d%d%d",&u,&v,&w,&cost);
        addedge(u,v,w,cost);
    }
    micmxf();
    printf("%d %d\n",maxflow,mincost);
    return 0;
}

网络流常用建图方法

BZOJ1001

最朴素的建模。
直接在原图上源点 \(s=1\) ,汇点 \(t=n*m\) 跑最大流即可。

网络流24题 飞行员配对方案问题

理解最大流和二分图匹配的关系。
本题构造一个二分图,对于所有左侧点 \(i\) ,连边 \((s,i,INF)\) ;对于所有右侧点 \(j\) ,连边 \((j,t,INF)\) ,对于二分图的每条边,连边 \((u,v,1)\)

网络流24题 最小路径覆盖问题

最小路径覆盖。
先将每个点 \(u\) 拆成一个入点 \(u_1\) 和一个出点 \(u_2\)
\((s,i_1,1),(i_2,t,1),(u_2,v_1,1),(i_1,i_2,1)\)
求最大流。

NOI2006 最大获利

对于每个物品有一个收益,选一些物品要付出一个代价,求最大收益的题目往往用最小割解决。
本题中,设 \(i\) 为中转站, \((a_j,b_j,c_j)\) 为用户需求,连边 \((s,i,p_i),(j,t,c_j),(a_j,b_j,INF)\) ,然后求最大流(最小割),答案为所有收益的和-最小割。可以发现最小割=最小成本+舍弃的收益。

网络流24题 方格取数问题

也用最小割解决。
先对图黑白染色,然后设 \(i\) 为黑点, \(j\) 为白点, \(k\) 为与 \(i\) 相邻的点,连边 \((s,i,a_i),(j,t,a_j),(i,k,INF)\)

CQOI2009 跳舞

先考虑如何验证给定的舞曲数目是否有解。
构造最大流,把每个人拆成两个点,男孩 \(i_1,i_2\) ,女孩 \(i_3,i_4\) ,连边 \((s,i_1,INF),(i_1,i_4,1),(i_1,i_2,k),(i_2,i_3,1),(i_3,i_4,k),(i_4,t,INF)\) ,如果最大流=舞曲数目*人数,那么可行,否则不可行。
然后我们只需要二分舞曲数目。

TJOI2015 线性代数

经过推算, \(D=\sum_{i=1}^n \sum_{j=1}^n a_i*a_j*b_{i,j}-\sum_{i=1}^n a_i*c_i\)
这和上面讲的最小割类似,故用最小割解决。
连边 \((s,i,c_i),(i,(i,j),INF),(j,(i,j),INF),((i,j),t,b_{i,j})\)

CQOI2012 交换棋子

费用流。
首先把每一个点拆成三个点 \(i_1,i_2,i_3\)
\(j\) 是与 \(i\) 八连通的点,若 \(i\) 初始状态为1,连边 \((s,i_2,1,0)\) ;若 \(i\) 末状态为1,连边 \((i_2,t,1,0)\)
对于所有节点,连边 \((i_1,i_2,w_1,0),(i_2,i_3,w_2,1),(i_3,j_1,INF,0)\) ,其中若 \(m_{i,j}\) 为奇数且 \((i,j)\) 初状态为1,末状态为0, \(w_1=\lfloor \frac{m}{2} \rfloor,w_2=\lfloor \frac{m+1}{2} \rfloor\) ;若 \(m_{i,j}\) 为奇数且 \((i,j)\) 初状态为0,末状态为1, \(w_1=\lfloor \frac{m+1}{2} \rfloor,w_2=\lfloor \frac{m}{2} \rfloor\) ;否则 \(w_1=\frac{m}{2},w_2=\frac{m}{2}\)

转载于:https://www.cnblogs.com/BlogOfchc1234567890/p/11178210.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值