• 【暖*墟】#网络流# 费用流的学习与练习


    最小费用最大流

    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    #include <queue>
    #include <stack>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    
    #define R register
    
    //【p2153】晨跑 [最小费用最大流]
    // 要求路程最短,天数尽量长。考虑:路程为费用,天数为流量。
    
    //由于每个点只能被访问一次,要进行拆点:将i拆成i1和i2,连边(i1,i2,1,0)(容量为1,费用为0),
    //对于有向图的每条边(u,v,w),连边(u2​,v1​,1,w)和其反向边(v1,u2,0,−w)。
    
    void reads(ll &x){ //读入优化(正负整数)
        ll f=1;x=0;char S=getchar();
        while(S<'0'||S>'9'){if(S=='-')f=-1;S=getchar();}
        while(S>='0'&&S<='9'){x=x*10+S-'0';S=getchar();}
        x*=f; //正负号
    }
    
    const ll N=100019;
    
    struct edge{ ll ver,nextt,flow,cost; }e[2*N];
    
    ll tot=-1,n,m,S,T,maxf=0,minc=0;
    
    ll flow[N],head[N],dist[N],inq[N],pre[N],lastt[N];
    
    void add(ll a,ll b,ll f,ll c)
    { e[++tot].nextt=head[a],head[a]=tot,
      e[tot].ver=b,e[tot].flow=f,e[tot].cost=c; } 
    
    bool spfa(ll S,ll T){
        queue<ll> q;
        memset(inq,0,sizeof(inq));
        memset(flow,0x7f,sizeof(flow));
        memset(dist,0x7f,sizeof(dist));
        q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1;
        while(!q.empty()){
            ll x=q.front(); q.pop(); inq[x]=0;
            for(ll i=head[x];i!=-1;i=e[i].nextt){
                if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){
                    dist[e[i].ver]=dist[x]+e[i].cost;
                    pre[e[i].ver]=x,lastt[e[i].ver]=i;
                    flow[e[i].ver]=min(flow[x],e[i].flow);
                    if(!inq[e[i].ver])
                        q.push(e[i].ver),inq[e[i].ver]=1;
                }
            }
        } return pre[T]!=-1;
    }
    
    void mcmf(){
        while(spfa(S,T)){
            ll now=T; //↓↓最小费用最大流
            maxf+=flow[T],minc+=dist[T]*flow[T];
            while(now!=S){ //↓↓正边流量-,反边流量+
                e[lastt[now]].flow-=flow[T];
                e[lastt[now]^1].flow+=flow[T]; 
                //↑↑利用xor1“成对储存”的性质
                now=pre[now]; //维护前向边last,前向点pre
            }
        }
    }
    
    int main(){
        scanf("%lld%lld",&n,&m); memset(head,-1,sizeof(head)); 
        //注意:一定要把head和tot初始化为-1,才能使用xor 1的性质
        for(ll i=1,x,y,c;i<=m;i++){
            scanf("%lld%lld%lld",&x,&y,&c);
            add(x+n,y,1,c),add(y,x+n,0,-c);
        } for(int i=1;i<=n;i++) add(i,i+n,1,0),add(i+n,i,0,0);
        S=1+n,T=n; //从点1的2号点走到点n的1号点
        mcmf(),printf("%lld %lld
    ",maxf,minc); //最大流&最小费用
    }
    【p2153】晨跑 //最小费用最大流の模板题
    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    #include <queue>
    #include <stack>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    
    #define R register
    
    /*【p4134】连连看 [最大费用最大流]
    给出一个闭区间[a,b],如果区间中的某两个数x,y的平方差x^2-y^2=完全平方数z^2,
    并且y与z互质,那么就可以将x和y连起来并且将它们一起消除,同时得到x+y点分数。
    那么过关的要求就是,消除的数对尽可能多的前提下,得到足够的分数。*/
    
    //【分析】消除对数尽量多:最大流;得到足够的分数:最大费用。
    
    //根据平方差公式,可以得到(x,y)互质。拆点,建双向边(i->j'和j->i',容量1,费用i+j)
    
    // -------------> 注意答案为:maxflow/2和maxcost/2。
    
    //【如何求最大费用最大流?】先将cost取相反数,跑最小费用最大流,再对费用取相反数。
    
    void reads(int &x){ //读入优化(正负整数)
        int fx=1;x=0;char ch_=getchar();
        while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();}
        while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();}
        x*=fx; //正负号
    }
    
    const int N=100019;
    
    int gcd(int x,int y){ return (y==0)?x:gcd(y,x%y); }
    
    struct edge{ int ver,nextt,flow,cost; }e[2*N];
    
    int tot=-1,n,a,b,S,T,maxf=0,minc=0;
    
    int flow[N],head[N],dist[N],inq[N],pre[N],lastt[N];
    
    void add(int a,int b,int f,int c)
    { e[++tot].nextt=head[a],head[a]=tot,
      e[tot].ver=b,e[tot].flow=f,e[tot].cost=c; } 
    
    bool spfa(int S,int T){
        queue<int> q;
        memset(inq,0,sizeof(inq));
        memset(flow,0x7f,sizeof(flow));
        memset(dist,0x7f,sizeof(dist));
        q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1;
        while(!q.empty()){
            int x=q.front(); q.pop(); inq[x]=0;
            for(int i=head[x];i!=-1;i=e[i].nextt){
                if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){
                    dist[e[i].ver]=dist[x]+e[i].cost;
                    pre[e[i].ver]=x,lastt[e[i].ver]=i;
                    flow[e[i].ver]=min(flow[x],e[i].flow);
                    if(!inq[e[i].ver])
                        q.push(e[i].ver),inq[e[i].ver]=1;
                }
            }
        } return pre[T]!=-1;
    }
    
    void mcmf(){
        while(spfa(S,T)){
            int now=T; //↓↓最小费用最大流
            maxf+=flow[T],minc+=dist[T]*flow[T];
            while(now!=S){ //↓↓正边流量-,反边流量+
                e[lastt[now]].flow-=flow[T];
                e[lastt[now]^1].flow+=flow[T]; 
                //↑↑利用xor1“成对储存”的性质
                now=pre[now]; //维护前向边last,前向点pre
            }
        }
    }
    
    int main(){
        reads(a),reads(b); n=b-a+1,S=0,T=2*n+1; // T=拆点总数+1
        memset(head,-1,sizeof(head)); //记得把head和tot初始化为-1
        for(int i=a,z;i<=b;i++) for(int j=a;j<i;j++){ //寻找满足的对数
            z=(int)round(sqrt(i*i-j*j)); //(其实没用到xy互质的性质...)
            if(z*z==i*i-j*j&&gcd(j,z)==1) //↓↓先将cost取相反,跑最小费用最大流
                add(i-a+1,j-a+1+n,1,-i-j),add(j-a+1+n,i-a+1,0,-(-i-j)),
                add(j-a+1,i-a+1+n,1,-i-j),add(i-a+1+n,j-a+1,0,-(-i-j));
        } for(int i=1;i<=n;i++) add(S,i,1,0),add(i,S,0,0),add(i+n,T,1,0),add(T,i+n,0,0);
        mcmf(),printf("%d %d
    ",maxf/2,-minc/2); //最大流&最大费用
    }
    【p4134】连连看 //最小费用最大流 + 数学分析
    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    #include <queue>
    #include <stack>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    
    #define R register
    
    /*【p2053】修车 [最小费用]
    同一时刻来了N位车主。有M位技术维修人员,不同人员对不同的车进行维修用时不同。
    现在需要安排这M位技术人员所维修的车及顺序,使得顾客平均等待的时间最小。
    说明:顾客的等待时间是指从他把车送至维修中心到维修完毕所用的时间。*/
    
    //【分析】把m个工人拆成n*m个,表示n个时间段。和n辆车完全相连(完全二分图)。
    // (费用)边的权值=工人时间段编号*输入的时间,表示需要等待的时间。最小费用/n平均。
    
    void reads(int &x){ //读入优化(正负整数)
        int fx=1;x=0;char ch_=getchar();
        while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();}
        while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();}
        x*=fx; //正负号
    }
    
    const int N=1000019;
    
    struct edge{ int ver,nextt,flow,cost; }e[2*N];
    
    int tot=-1,n,m,S,T,maxf=0,minc=0,tt[91][19];
    
    int flow[N],head[N],dist[N],inq[N],pre[N],lastt[N];
    
    void add(int a,int b,int f,int c)
    { e[++tot].nextt=head[a],head[a]=tot,
      e[tot].ver=b,e[tot].flow=f,e[tot].cost=c; } 
    
    bool spfa(int S,int T){
        queue<int> q;
        memset(inq,0,sizeof(inq));
        memset(flow,0x7f,sizeof(flow));
        memset(dist,0x7f,sizeof(dist));
        q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1;
        while(!q.empty()){
            int x=q.front(); q.pop(); inq[x]=0;
            for(int i=head[x];i!=-1;i=e[i].nextt){
                if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){
                    dist[e[i].ver]=dist[x]+e[i].cost;
                    pre[e[i].ver]=x,lastt[e[i].ver]=i;
                    flow[e[i].ver]=min(flow[x],e[i].flow);
                    if(!inq[e[i].ver])
                        q.push(e[i].ver),inq[e[i].ver]=1;
                }
            }
        } return pre[T]!=-1;
    }
    
    void mcmf(){
        while(spfa(S,T)){
            int now=T; //↓↓最小费用最大流
            maxf+=flow[T],minc+=dist[T]*flow[T];
            while(now!=S){ //↓↓正边流量-,反边流量+
                e[lastt[now]].flow-=flow[T];
                e[lastt[now]^1].flow+=flow[T]; 
                //↑↑利用xor1“成对储存”的性质
                now=pre[now]; //维护前向边last,前向点pre
            }
        }
    }
    
    int main(){
        reads(m),reads(n); S=0,T=n+n*m+1; memset(head,-1,sizeof(head));
        for(int i=1;i<=n;i++) add(i+n*m,T,1,0),add(T,i+n*m,0,0);
        for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) //第j个工人的第n个时间段
            reads(tt[i][j]),add(S,(i-1)*m+j,1,0),add((i-1)*m+j,S,0,0); 
          //↑↑注意:s和每个点都要连(可能同时出发)[很巧妙的细节处理方法]
        for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) for(int k=1;k<=m;k++) 
            add((j-1)*m+k,i+n*m,1,tt[i][k]*j),add(i+n*m,(j-1)*m+k,0,-(tt[i][k]*j));
        //j相当于还剩下的人数,如果在这里花tt[i][k],后面的j个人(包括自己)都会+tt[i][k];
        mcmf(),printf("%.2lf
    ",(double)minc/n); //平均最小费用(耗时)
    }
    【p2053】修车 // 最小费用 + 思维较难
    // luogu-judger-enable-o2
    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    #include <queue>
    #include <stack>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    
    #define R register
    
    /*【p4209】学习小组 [建图困难的最小费用流+条件分析]
    共有n个学生,m个学习小组,规定一个学生最多参加k个学习小组。
    每个学生参加学习小组要交一定的手续费 /// 学校对学习小组奖励 Ci *a(人数)^2 元。
    在参与学生尽量多的情况下,求财务处最少要支出多少钱(若为负数,则输出负数)。*/
    
    /*【分析】由于有Ci*a^2的存在,使得正常加边的费用流无法处理。
    每个学习小组向T连:容量为1,费用为Ci*1、Ci*3、Ci*5、Ci*7、...的一堆边(利用平方差关系)。
    S向每个学生连容量为k(最多k个小组)、费用为0的边,学生向能参加的学习小组连容量为1、费用为Fi的边。
    “在参与学生(而不是每个学习小组的人数总和)尽量多的情况下”指的是所有学生必须有流通过,但不必满流。
    所以还要从每个学生向T连一条容量为k-1,费用为0的边,保证费用最小(只参加1个小组)。 */
    
    void reads(int &x){ //读入优化(正负整数)
        int fx=1;x=0;char ch_=getchar();
        while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();}
        while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();}
        x*=fx; //正负号
    }
    
    const int N=500019;
    
    struct edge{ int ver,nextt,flow,cost; }e[2*N];
    
    int tot=-1,n,m,k,S,T,maxf=0,minc=0,f[N];
    
    int flow[N],head[N],dist[N],inq[N],pre[N],lastt[N];
    
    void add(int a,int b,int f,int c)
    { e[++tot].nextt=head[a],head[a]=tot,
      e[tot].ver=b,e[tot].flow=f,e[tot].cost=c; } 
    
    bool spfa(int S,int T){
        queue<int> q;
        memset(inq,0,sizeof(inq));
        memset(flow,0x7f,sizeof(flow));
        memset(dist,0x7f,sizeof(dist));
        q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1;
        while(!q.empty()){
            int x=q.front(); q.pop(); inq[x]=0;
            for(int i=head[x];i!=-1;i=e[i].nextt){
                if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){
                    dist[e[i].ver]=dist[x]+e[i].cost;
                    pre[e[i].ver]=x,lastt[e[i].ver]=i;
                    flow[e[i].ver]=min(flow[x],e[i].flow);
                    if(!inq[e[i].ver])
                        q.push(e[i].ver),inq[e[i].ver]=1;
                }
            }
        } return pre[T]!=-1;
    }
    
    void mcmf(){
        while(spfa(S,T)){
            int now=T; //↓↓最小费用最大流
            maxf+=flow[T],minc+=dist[T]*flow[T];
            while(now!=S){ //↓↓正边流量-,反边流量+
                e[lastt[now]].flow-=flow[T];
                e[lastt[now]^1].flow+=flow[T]; 
                //↑↑利用xor1“成对储存”的性质
                now=pre[now]; //维护前向边last,前向点pre
            }
        }
    }
    
    char ss[N]; //每个学生可以加入的小组
    
    int main(){
        reads(n),reads(m),reads(k); S=0,T=n+m+1; memset(head,-1,sizeof(head));
        for(int i=1,x;i<=m;i++){ reads(x); //小组i(编号i+n)向T连多条费用不同的边
            for(int j=1;j<=n;j++) add(i+n,T,1,x*(2*j-1)),add(T,i+n,0,-x*(2*j-1));
        } for(int i=1;i<=m;i++) reads(f[i]); //参加小组i的费用
        for(int i=1;i<=n;i++){ //↓↓从每个学生向T连边,保证每人都有流的情况下费用最小
            add(S,i,k,0),add(i,S,0,0),add(i,T,k-1,0),add(T,i,0,0);
            scanf("%s",ss+1); for(int j=1;j<=m;j++) if(ss[j]=='1') //每人能报的小组
                add(i,j+n,1,-f[j]),add(j+n,i,0,-(-f[j])); //收入用'-'表示
        } mcmf(),printf("%d
    ",minc); //在有前提条件の建边情况下的最小费用
    }
    【p4209】学习小组 // 建图困难的最小费用流 + 条件分析
    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    #include <queue>
    #include <stack>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    
    #define R register
    
    /*【p2517】订货 
    在第i个月对某产品的需求量为Ui,该产品的订货单价为di,
    仓库容量为k,上个月月底未销完的产品要付单位存贮费用m,
    假定第一月月初的库存量为零,第n月月底的库存量也为零,
    问如何安排这n个月订购计划,才能使成本最低?*/
    
    /*【分析】S->i c=inf f=di; i->T c=ui f=0; i->i+1 c=k f=m。
    即,如此图:https://cdn.luogu.org/upload/pic/21514.png 建图。 */
    
    void reads(int &x){ //读入优化(正负整数)
        int fx=1;x=0;char ch_=getchar();
        while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();}
        while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();}
        x*=fx; //正负号
    }
    
    const int N=500019,INF=0x3f3f3f3f;
    
    struct edge{ int ver,nextt,flow,cost; }e[2*N];
    
    int tot=-1,n,m,k,S,T,maxf=0,minc=0,f[N];
    
    int flow[N],head[N],dist[N],inq[N],pre[N],lastt[N];
    
    void add(int a,int b,int f,int c)
    { e[++tot].nextt=head[a],head[a]=tot,
      e[tot].ver=b,e[tot].flow=f,e[tot].cost=c; } 
    
    bool spfa(int S,int T){
        queue<int> q;
        memset(inq,0,sizeof(inq));
        memset(flow,0x7f,sizeof(flow));
        memset(dist,0x7f,sizeof(dist));
        q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1;
        while(!q.empty()){
            int x=q.front(); q.pop(); inq[x]=0;
            for(int i=head[x];i!=-1;i=e[i].nextt){
                if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){
                    dist[e[i].ver]=dist[x]+e[i].cost;
                    pre[e[i].ver]=x,lastt[e[i].ver]=i;
                    flow[e[i].ver]=min(flow[x],e[i].flow);
                    if(!inq[e[i].ver])
                        q.push(e[i].ver),inq[e[i].ver]=1;
                }
            }
        } return pre[T]!=-1;
    }
    
    void mcmf(){
        while(spfa(S,T)){
            int now=T; //↓↓最小费用最大流
            maxf+=flow[T],minc+=dist[T]*flow[T];
            while(now!=S){ //↓↓正边流量-,反边流量+
                e[lastt[now]].flow-=flow[T];
                e[lastt[now]^1].flow+=flow[T]; 
                //↑↑利用xor1“成对储存”的性质
                now=pre[now]; //维护前向边last,前向点pre
            }
        }
    }
    
    int main(){
        reads(n),reads(m),reads(k); S=0,T=n+1; 
        int x; memset(head,-1,sizeof(head));
        for(int i=1;i<=n;i++) reads(x),add(i,T,x,0),
            add(T,i,0,0); // ↑↑ i->T c=ui(需求量) f=0;
        for(int i=1;i<=n;i++) reads(x),add(S,i,INF,x),
            add(i,S,0,-x); // ↑↑ S->i c=inf f=di(单位流量费用);
        for(int i=1;i<n;i++) add(i,i+1,k,m),add(i+1,i,0,-m); //m:单位储存费用
        mcmf(),printf("%d
    ",minc); //输出最小费用
    }
    【p2517】订货 // 最小费用最大流 + 题意分析
    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    #include <queue>
    #include <stack>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    
    #define R register
    
    /*【p3980】志愿者招募 [神奇的建图方式]
    项目需要N天才能完成,其中第i天至少需要Ai个人。 
    有M类志愿者,其中第i类可以从第Si天工作到第Ti天,费用Ci元。
    希望用尽量少的费用招募足够的志愿者。 */
    
    /*【分析】S连第一天,最后一天连T,这两条边容量为inf,费用为0(即通行无阻)。
    每天向后一天连边,c=INF-a[i],f=0,沿时间流依次解决每一天的问题。
    将每一类志愿者的s[i]与t[i]+1连一条:c=INF,f=c[i]的边。 */
    
    //可以看 https://www.luogu.org/blog/user7035/solution-p3980 的分析
    
    void reads(int &x){ //读入优化(正负整数)
        int fx=1;x=0;char ch_=getchar();
        while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();}
        while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();}
        x*=fx; //正负号
    }
    
    const int N=500019,INF=0x3f3f3f3f;
    
    struct edge{ int ver,nextt,flow,cost; }e[2*N];
    
    int tot=-1,n,m,k,S,T,maxf=0,minc=0,f[N];
    
    int flow[N],head[N],dist[N],inq[N],pre[N],lastt[N];
    
    void add(int a,int b,int f,int c)
    { e[++tot].nextt=head[a],head[a]=tot,
      e[tot].ver=b,e[tot].flow=f,e[tot].cost=c; } 
    
    bool spfa(int S,int T){
        queue<int> q;
        memset(inq,0,sizeof(inq));
        memset(flow,0x7f,sizeof(flow));
        memset(dist,0x7f,sizeof(dist));
        q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1;
        while(!q.empty()){
            int x=q.front(); q.pop(); inq[x]=0;
            for(int i=head[x];i!=-1;i=e[i].nextt){
                if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){
                    dist[e[i].ver]=dist[x]+e[i].cost;
                    pre[e[i].ver]=x,lastt[e[i].ver]=i;
                    flow[e[i].ver]=min(flow[x],e[i].flow);
                    if(!inq[e[i].ver])
                        q.push(e[i].ver),inq[e[i].ver]=1;
                }
            }
        } return pre[T]!=-1;
    }
    
    void mcmf(){
        while(spfa(S,T)){
            int now=T; //↓↓最小费用最大流
            maxf+=flow[T],minc+=dist[T]*flow[T];
            while(now!=S){ //↓↓正边流量-,反边流量+
                e[lastt[now]].flow-=flow[T];
                e[lastt[now]^1].flow+=flow[T]; 
                //↑↑利用xor1“成对储存”的性质
                now=pre[now]; //维护前向边last,前向点pre
            }
        }
    }
    
    int main(){
        reads(n),reads(m); S=0,T=n+2; 
        memset(head,-1,sizeof(head)); //↓↓注意,要到n+1
        for(int i=1,ai;i<=n;i++) reads(ai),add(i,i+1,INF-ai,0),add(i+1,i,0,0);
        add(S,1,INF,0),add(1,S,0,0); add(n+1,T,INF,0),add(T,n+1,0,0);
        for(int i=1,si,ti,ci;i<=m;i++) reads(si),reads(ti),reads(ci),
            add(si,ti+1,INF,ci),add(ti+1,si,0,-ci);
        mcmf(),printf("%d
    ",minc); //输出最小费用
    }
    【p3980】志愿者招募 // 最小费用最大流 + 神奇的建图方式
    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    #include <queue>
    #include <stack>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    
    #define R register
    
    /*【p3159】交换棋子 [0/1网格图费用流]
    n行m列的黑白棋盘,每次可以交换两个相邻格子(八连通)中的棋子,
    要求第i行第j列的格子只能参与mi,j次交换。求达到目标状态的最小交换总次数。 */
    
    /*【分析】感觉GXZdalao真的超强超强的√ 分析思路超级清楚啊啊啊啊啊 成绩还高qwq 
    我这个蒟蒻就只好来放链接了QAQ https://www.cnblogs.com/GXZlegend/p/8310945.html */
    
    void reads(int &x){ //读入优化(正负整数)
        int fx=1;x=0;char ch_=getchar();
        while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();}
        while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();}
        x*=fx; //正负号
    }
    
    const int N=500019,M=1217,INF=0x3f3f3f3f;
    
    struct edge{ int ver,nextt,flow,cost; }e[2*N];
    
    int tot=-1,n,m,k,S,T,maxf=0,minc=0,a[M][M],b[M][M],c[M][M];
    
    int flow[N],head[N],dist[N],inq[N],pre[N],lastt[N];
    
    void add(int a,int b,int f,int c)
    { e[++tot].nextt=head[a],head[a]=tot,e[tot].ver=b,e[tot].flow=f,e[tot].cost=c;
      e[++tot].nextt=head[a],head[a]=tot,e[tot].ver=b,e[tot].flow=0,e[tot].cost=-c; } 
    
    bool spfa(int S,int T){
        queue<int> q;
        memset(inq,0,sizeof(inq));
        memset(flow,0x7f,sizeof(flow));
        memset(dist,0x7f,sizeof(dist));
        q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1;
        while(!q.empty()){
            int x=q.front(); q.pop(); inq[x]=0;
            for(int i=head[x];i!=-1;i=e[i].nextt){
                if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){
                    dist[e[i].ver]=dist[x]+e[i].cost;
                    pre[e[i].ver]=x,lastt[e[i].ver]=i;
                    flow[e[i].ver]=min(flow[x],e[i].flow);
                    if(!inq[e[i].ver])
                        q.push(e[i].ver),inq[e[i].ver]=1;
                }
            }
        } return pre[T]!=-1;
    }
    
    void mcmf(){
        while(spfa(S,T)){
            int now=T; //↓↓最小费用最大流
            maxf+=flow[T],minc+=dist[T]*flow[T];
            while(now!=S){ //↓↓正边流量-,反边流量+
                e[lastt[now]].flow-=flow[T];
                e[lastt[now]^1].flow+=flow[T]; 
                //↑↑利用xor1“成对储存”的性质
                now=pre[now]; //维护前向边last,前向点pre
            }
        }
    }
    
    char ss[1519];
    
    int pos(int i,int j,int k){ return k*n*m+(i-1)*m+j; } //点的编号
    
    int main(){
    
        reads(n),reads(m); S=0,T=3*n*m+1; //每个点拆成三个点(入度、出度、中间点) 
    
        memset(head,-1,sizeof(head)); int sum1=0,sum2=0;
    
        for(int i=1;i<=n;i++){ cin>>ss; for(int j=1;j<=m;j++) a[i][j]=ss[j-1]^'0'; } //
        for(int i=1;i<=n;i++){ cin>>ss; for(int j=1;j<=m;j++) b[i][j]=ss[j-1]^'0'; } //
        for(int i=1;i<=n;i++){ cin>>ss; for(int j=1;j<=m;j++) c[i][j]=ss[j-1]^'0'; } //次数限制
    
        for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){
            
            if(!c[i][j]&&a[i][j]!=b[i][j]){ puts("-1"); return 0; }
            
            add(pos(i,j,0),pos(i,j,1),(c[i][j]-a[i][j]+b[i][j])>>1,0); //入度
            add(pos(i,j,1),pos(i,j,2),(c[i][j]+a[i][j]-b[i][j])>>1,0); //出度
            
            if(a[i][j]) add(S,pos(i,j,1),1,0),sum1++;
            if(b[i][j]) add(pos(i,j,1),T,1,0),sum2++;
    
            //↓↓相邻的点之间互相连边(出度--入度),c=inf,f=1。
    
            if(i>1) add(pos(i,j,2),pos(i-1,j,0),INF,1);
            if(i<n) add(pos(i,j,2),pos(i+1,j,0),INF,1);
            if(j>1) add(pos(i,j,2),pos(i,j-1,0),INF,1);
            if(j<m) add(pos(i,j,2),pos(i,j+1,0),INF,1);
            
            if(i>1&&j>1) add(pos(i,j,2),pos(i-1,j-1,0),INF,1);
            if(i>1&&j<m) add(pos(i,j,2),pos(i-1,j+1,0),INF,1);
            if(i<n&&j>1) add(pos(i,j,2),pos(i+1,j-1,0),INF,1);
            if(i<n&&j<m) add(pos(i,j,2),pos(i+1,j+1,0),INF,1);
        }
    
        if(sum1!=sum2){ puts("-1"); return 0; } //黑白棋子数不同,一定无解
    
        mcmf(); if(sum1!=maxf) puts("-1"); else printf("%d
    ",minc);
    }
    【p3159】交换棋子 // 0/1网格图费用流 //我的TLE代码...
    #include <queue>
    #include <cstdio>
    #include <cstring>
    #define N 1210
    #define M 121000
    #define inf 1 << 30
    #define pos(i , j , k) (k * n * m + (i - 1) * m + j)
    using namespace std;
    queue<int> q;
    int a[25][25] , b[25][25] , c[25][25] , head[N] , to[M] , val[M] , cost[M] , next[M] , cnt = 1 , s , t , dis[N] , from[N] , pre[N];
    inline void add(int x , int y , int v , int c)
    {
        to[++cnt] = y , val[cnt] = v , cost[cnt] = c , next[cnt] = head[x] , head[x] = cnt;
        to[++cnt] = x , val[cnt] = 0 , cost[cnt] = -c , next[cnt] = head[y] , head[y] = cnt;
    }
    bool spfa()
    {
        int x , i;
        memset(from , -1 , sizeof(from));
        memset(dis , 0x3f , sizeof(dis));
        dis[s] = 0 , q.push(s);
        while(!q.empty())
        {
            x = q.front() , q.pop();
            for(i = head[x] ; i ; i = next[i])
                if(val[i] && dis[to[i]] > dis[x] + cost[i])
                    dis[to[i]] = dis[x] + cost[i] , from[to[i]] = x , pre[to[i]] = i , q.push(to[i]);
        }
        return ~from[t];
    }
    inline int rnum()
    {
        char ch = getchar();
        while(ch < '0' || ch > '9') ch = getchar();
        return ch ^ '0';
    }
    int main()
    {
        int n , m , i , j , sum1 = 0 , sum2 = 0 , ans = 0;
        scanf("%d%d" , &n , &m) , s = 0 , t = 3 * n * m + 1;
        for(i = 1 ; i <= n ; i ++ ) for(j = 1 ; j <= m ; j ++ ) a[i][j] = rnum();
        for(i = 1 ; i <= n ; i ++ ) for(j = 1 ; j <= m ; j ++ ) b[i][j] = rnum();
        for(i = 1 ; i <= n ; i ++ ) for(j = 1 ; j <= m ; j ++ ) c[i][j] = rnum();
        for(i = 1 ; i <= n ; i ++ )
        {
            for(j = 1 ; j <= m ; j ++ )
            {
                if(!c[i][j] && a[i][j] != b[i][j])
                {
                    puts("-1");
                    return 0;
                }
                add(pos(i , j , 0) , pos(i , j , 1) , (c[i][j] - a[i][j] + b[i][j]) >> 1 , 0);
                add(pos(i , j , 1) , pos(i , j , 2) , (c[i][j] + a[i][j] - b[i][j]) >> 1 , 0);
                if(a[i][j]) add(s , pos(i , j , 1) , 1 , 0) , sum1 ++ ;
                if(b[i][j]) add(pos(i , j , 1) , t , 1 , 0) , sum2 ++ ;
                if(i > 1) add(pos(i , j , 2) , pos(i - 1 , j , 0) , inf , 1);
                if(i < n) add(pos(i , j , 2) , pos(i + 1 , j , 0) , inf , 1);
                if(j > 1) add(pos(i , j , 2) , pos(i , j - 1 , 0) , inf , 1);
                if(j < m) add(pos(i , j , 2) , pos(i , j + 1 , 0) , inf , 1);
                if(i > 1 && j > 1) add(pos(i , j , 2) , pos(i - 1 , j - 1 , 0) , inf , 1);
                if(i > 1 && j < m) add(pos(i , j , 2) , pos(i - 1 , j + 1 , 0) , inf , 1);
                if(i < n && j > 1) add(pos(i , j , 2) , pos(i + 1 , j - 1 , 0) , inf , 1);
                if(i < n && j < m) add(pos(i , j , 2) , pos(i + 1 , j + 1 , 0) , inf , 1);
            }
        }
        if(sum1 != sum2) puts("-1");
        else
        {
            while(spfa())
            {
                j = inf;
                for(i = t ; i != s ; i = from[i]) j = min(j , val[pre[i]]);
                sum1 -= j , ans += j * dis[t];
                for(i = t ; i != s ; i = from[i]) val[pre[i]] -= j , val[pre[i] ^ 1] += j;
            }
            if(sum1) puts("-1");
            else printf("%d
    " , ans);
        }
        return 0;
    }
    【p3159】交换棋子 // 0/1网格图费用流 //大佬的AC代码...
    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    #include <queue>
    #include <stack>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    
    #define R register
    
    /*【p2604】网络扩容
    有向图,每条边都有一个容量V和一个扩容费用C(将容量扩大1)。
    求:1. 不扩容时1到N的最大流; 2. 将1到N的最大流增加K所需的最小扩容费用。*/
    
    /*【分析】最大流也可以用费用流求出2333 第2问建立超级源点S就行了(有INF的容量即可) */
    
    void reads(int &x){ //读入优化(正负整数)
        int fx=1;x=0;char ch_=getchar();
        while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();}
        while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();}
        x*=fx; //正负号
    }
    
    const int N=500019,INF=0x3f3f3f3f;
    
    struct edge{ int ver,nextt,flow,cost; }e[2*N];
    
    int tot=-1,n,m,k,S,T,maxf=0,minc=0,x[N],y[N],v[N],c[N];
    
    int flow[N],head[N],dist[N],inq[N],pre[N],lastt[N];
    
    void add(int a,int b,int f,int c)
    { e[++tot].nextt=head[a],head[a]=tot,
      e[tot].ver=b,e[tot].flow=f,e[tot].cost=c; } 
    
    bool spfa(int S,int T){
        queue<int> q;
        memset(inq,0,sizeof(inq));
        memset(flow,0x7f,sizeof(flow));
        memset(dist,0x7f,sizeof(dist));
        q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1;
        while(!q.empty()){
            int x=q.front(); q.pop(); inq[x]=0;
            for(int i=head[x];i!=-1;i=e[i].nextt){
                if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){
                    dist[e[i].ver]=dist[x]+e[i].cost;
                    pre[e[i].ver]=x,lastt[e[i].ver]=i;
                    flow[e[i].ver]=min(flow[x],e[i].flow);
                    if(!inq[e[i].ver])
                        q.push(e[i].ver),inq[e[i].ver]=1;
                }
            }
        } return pre[T]!=-1;
    }
    
    void mcmf(){
        while(spfa(S,T)){
            int now=T; //↓↓最小费用最大流
            maxf+=flow[T],minc+=dist[T]*flow[T];
            while(now!=S){ //↓↓正边流量-,反边流量+
                e[lastt[now]].flow-=flow[T];
                e[lastt[now]^1].flow+=flow[T]; 
                //↑↑利用xor1“成对储存”的性质
                now=pre[now]; //维护前向边last,前向点pre
            }
        }
    }
    
    int main(){
        reads(n),reads(m),reads(k);
        memset(head,-1,sizeof(head));
        for(int i=1;i<=m;i++) reads(x[i]),reads(y[i]), //初始最大流
            reads(v[i]),reads(c[i]),add(x[i],y[i],v[i],0),add(y[i],x[i],0,0); 
        S=1,T=n; mcmf(),printf("%d ",maxf); minc=0,maxf=0; //初始,S=1,T=n
        S=0; add(S,1,k,0),add(1,S,0,0); //超级源点S(求1~N的最大流,所以只用向1连边)
        for(int i=1;i<=m;i++) add(x[i],y[i],INF,c[i]),add(y[i],x[i],0,-c[i]);
        mcmf(),printf("%d
    ",minc); //输出最小费用
    }
    【p2604】网络扩容 //将1到N的最大流增加K所需的最小扩容费用:add(S,1,k,0)
    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    #include <queue>
    #include <stack>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    
    #define R register
    
    //【p3965】循环格
    
    /*【分析】让源点S的流为inf,到每个节点的入点连一条容量1的边。
    每个节点的入点 向 他在原图中指向的节点的出点 连一条容量为1的边。
    每个节点的出点 向 汇点T 连一条容量为1的边。跑一次最大流。
    判断最终的流是否等于n*m(就是原图中的总节点数),则为循环格。*/
    
    /*【深入】最大流只能判定,如何修改?费用流。 */
    
    void reads(int &x){ //读入优化(正负整数)
        int f=1;x=0;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
        x*=f; //正负号
    }
    
    const int N=10019;
    
    struct edge{ int ver,nextt,flow,cost; }e[2*N];
    
    int tot=-1,n,m,S,T,maxf=0,minc=0;
    
    int flow[N],head[N],dist[N],inq[N],pre[N],lastt[N];
    
    void add(int a,int b,int f,int c)
    { e[++tot].nextt=head[a],head[a]=tot,
      e[tot].ver=b,e[tot].flow=f,e[tot].cost=c;
      e[++tot].nextt=head[b],head[b]=tot,
      e[tot].ver=a,e[tot].flow=0,e[tot].cost=-c; } 
    
    const char dire[4]={'U','R','D','L'};
    
    const int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
    
    bool spfa(int S,int T){
        queue<int> q;
        memset(inq,0,sizeof(inq));
        memset(flow,0x7f,sizeof(flow));
        memset(dist,0x7f,sizeof(dist));
        q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1;
        while(!q.empty()){
            int x=q.front(); q.pop(); inq[x]=0;
            for(int i=head[x];i!=-1;i=e[i].nextt){
                if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){
                    dist[e[i].ver]=dist[x]+e[i].cost;
                    pre[e[i].ver]=x,lastt[e[i].ver]=i;
                    flow[e[i].ver]=min(flow[x],e[i].flow);
                    if(!inq[e[i].ver])
                        q.push(e[i].ver),inq[e[i].ver]=1;
                }
            }
        } return pre[T]!=-1;
    }
    
    void mcmf(){
        while(spfa(S,T)){
            int now=T; //↓↓最小费用最大流
            maxf+=flow[T],minc+=dist[T]*flow[T];
            while(now!=S){ //↓↓正边流量-,反边流量+
                e[lastt[now]].flow-=flow[T];
                e[lastt[now]^1].flow+=flow[T]; 
                //↑↑利用xor1“成对储存”的性质
                now=pre[now]; //维护前向边last,前向点pre
            }
        }
    }
    
    void get_c(char &ch){ch=getchar();while(ch<'A'||ch>'Z')ch=getchar();}
    
    int main(){ 
        memset(head,-1,sizeof(head));
        reads(n),reads(m); S=0,T=N-1; char ch;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++){
                get_c(ch),add(S,(i-1)*m+j,1,0),add(n*m+(i-1)*m+j,T,1,0);
                for(int k=0;k<4;k++){ 
                    int xx=(i+dx[k]+n-1)%n+1,yy=(j+dy[k]+m-1)%m+1;
                    add((i-1)*m+j,n*m+(xx-1)*m+yy,1,ch!=dire[k]);
                }
        } mcmf(); cout<<minc<<endl; return 0;
    }
    【p3965】循环格 //玄学的图上费用流问题...

    有上下界的费用流

    【p4043】支线剧情:求把所有支线剧情都看完需要的min时间。

    【有上下界费用流的建图方法】 1.建立超级源汇S和T。

    2.判断是否有要求的源汇,从汇点向源点(此题中为1节点)连容量为inf,费用为0的边。

    3.对于原图x->y,下界为low,上界为high,连S->y,容量为low,费用为原费用;

    4.同时x->y边容量变为high-low(此题中为inf-1=inf),费用不变。

    5.对于每个点x,连x->T,容量为原图中x连向其它点的所有边的low之和(即出度cd[ ]),费用为0。

    题目要求每条边都需要走一次,所以每条边下界为1,上界为inf。

    然后转化为普通费用流来求。本题中具体建图为:

     i[i>1]->1 (inf,0),S->y (1,z),x->y (inf,z),x->T (cd[x],0)。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    #include<set>
    using namespace std;
    typedef long long ll;
    
    //【p4043】支线剧情 //有上下界的费用流
    //求:把所有支线剧情都看完需要的min时间。
    
    //果然大家都喜欢玩RPG游戏的吗(我昨天过Shin的单人线都玩到1:30...)
    //果然所谓的“快进”都是骗人的吗...(过剧情真心痛苦...)
    
    /* 有上下界费用流的建图方法: 1.建立超级源汇S和T。
    
    2.判断是否有要求的源汇,从汇点向源点(此题中为1节点)连容量为inf,费用为0的边。
    
    3.对于原图x->y,下界为low,上界为high,连S->y,容量为low,费用为原费用;
    
    4.同时x->y边容量变为high-low(此题中为inf-1=inf),费用不变。
    
    5.对于每个点x,连x->T,容量为原图中x连向其它点的所有边的low之和(即出度cd[]),费用为0。*/
    
    /*【分析】题目要求每条边都需要走一次,所以每条边下界为1,上界为inf。
    然后转化为普通费用流来求。本题中具体建图为:
     i[i>1]->1 (inf,0),S->y (1,z),x->y (inf,z),x->T (cd[x],0)。*/
    
    void reads(int &x){ //读入优化(正负整数)
        int fx=1;x=0;char ch_=getchar();
        while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();}
        while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();}
        x*=fx; //正负号
    }
    
    const int N=100019,inf=0x3f3f3f3f;
    
    struct edge{ int ver,nextt,flow,cost; }e[2*N];
    
    int tot=-1,n,S,T,maxf=0,minc=0,f[N];
    
    int flow[N],head[N],dist[N],inq[N],pre[N],lastt[N];
    
    void add(int a,int b,int f,int c)
    { e[++tot].nextt=head[a],head[a]=tot,
      e[tot].ver=b,e[tot].flow=f,e[tot].cost=c;
      e[++tot].nextt=head[b],head[b]=tot,
      e[tot].ver=a,e[tot].flow=0,e[tot].cost=-c; } 
    
    bool spfa(int S,int T){
        queue<int> q;
        memset(inq,0,sizeof(inq));
        memset(flow,0x7f,sizeof(flow));
        memset(dist,0x7f,sizeof(dist));
        q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1;
        while(!q.empty()){
            int x=q.front(); q.pop(); inq[x]=0;
            for(int i=head[x];i!=-1;i=e[i].nextt){
                if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){
                    dist[e[i].ver]=dist[x]+e[i].cost;
                    pre[e[i].ver]=x,lastt[e[i].ver]=i;
                    flow[e[i].ver]=min(flow[x],e[i].flow);
                    if(!inq[e[i].ver])
                        q.push(e[i].ver),inq[e[i].ver]=1;
                }
            }
        } return pre[T]!=-1;
    }
    
    void mcmf(){
        while(spfa(S,T)){
            int now=T; //↓↓最小费用最大流
            maxf+=flow[T],minc+=dist[T]*flow[T];
            while(now!=S){ //↓↓正边流量-,反边流量+
                e[lastt[now]].flow-=flow[T];
                e[lastt[now]^1].flow+=flow[T]; 
                //↑↑利用xor1“成对储存”的性质
                now=pre[now]; //维护前向边last,前向点pre
            }
        }
    }
    
    int main(){
        reads(n); S=0,T=n+1; memset(head,-1,sizeof(head));
        for(int i=1,k,y,z;i<=n;i++)
         { reads(k); if(k) add(i,T,k,0); if(i!=1) add(i,1,inf,0);
           while(k--) reads(y),reads(z),add(S,y,1,z),add(i,y,inf,z); } 
        mcmf(); printf("%d
    ",minc); return 0; //↑↑注意建图方式
    }
    【p4043】支线剧情 //有上下界的费用流【可能是因为费用流板子不够好所以只有70...】
    // luogu-judger-enable-o2
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    #include<set>
    using namespace std;
    typedef long long ll;
    
    //【p4043】80人环游地球
    
    /* 有上下界费用流的建图方法: 1.建立超级源汇S和T。
    
    2.判断是否有要求的源汇,从汇点向源点(此题中为每个i)连容量为inf,费用为0的边。
    
    3.对于原图x->y,下界为low,上界为high,连S->y,容量为low,费用为原费用;
    
    4.同时x->y边容量变为high-low(此题中为inf-1=inf),费用不变。
    
    5.对于每个点x,连x->T,容量为原图中x连向其它点的所有边的low之和(即出度cd[]),费用为0。*/
    
    /*【分析】题目要求每个点需要经过固定的次数vi,可以拆成两个点、之间连一条上下界均为vi的边。
    ss=0,tt=2*n+1,tt->ss(m,0)(用于限制流量,即人数); ss->i(inf,0),i+n->tt(inf,0) ;
    S=2*n+2,T=2*n+3,S->i+n(vi,0),i->T(vi(出度),0) ; i+n->j(inf,cost[i][j])(i<j)*/
    
    void reads(int &x){ //读入优化(正负整数)
        int fx=1;x=0;char ch_=getchar();
        while(ch_<'0'||ch_>'9'){if(ch_=='-')fx=-1;ch_=getchar();}
        while(ch_>='0'&&ch_<='9'){x=x*10+ch_-'0';ch_=getchar();}
        x*=fx; //正负号
    }
    
    const int N=100019,inf=0x3f3f3f3f;
    
    struct edge{ int ver,nextt,flow,cost; }e[2*N];
    
    int tot=-1,n,m,S,T,maxf=0,minc=0,f[N];
    
    int flow[N],head[N],dist[N],inq[N],pre[N],lastt[N];
    
    void add(int a,int b,int f,int c)
    { e[++tot].nextt=head[a],head[a]=tot,
      e[tot].ver=b,e[tot].flow=f,e[tot].cost=c;
      e[++tot].nextt=head[b],head[b]=tot,
      e[tot].ver=a,e[tot].flow=0,e[tot].cost=-c; } 
    
    bool spfa(int S,int T){
        queue<int> q;
        memset(inq,0,sizeof(inq));
        memset(flow,0x7f,sizeof(flow));
        memset(dist,0x7f,sizeof(dist));
        q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1;
        while(!q.empty()){
            int x=q.front(); q.pop(); inq[x]=0;
            for(int i=head[x];i!=-1;i=e[i].nextt){
                if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){
                    dist[e[i].ver]=dist[x]+e[i].cost;
                    pre[e[i].ver]=x,lastt[e[i].ver]=i;
                    flow[e[i].ver]=min(flow[x],e[i].flow);
                    if(!inq[e[i].ver])
                        q.push(e[i].ver),inq[e[i].ver]=1;
                }
            }
        } return pre[T]!=-1;
    }
    
    void mcmf(){
        while(spfa(S,T)){
            int now=T; //↓↓最小费用最大流
            maxf+=flow[T],minc+=dist[T]*flow[T];
            while(now!=S){ //↓↓正边流量-,反边流量+
                e[lastt[now]].flow-=flow[T];
                e[lastt[now]^1].flow+=flow[T]; 
                //↑↑利用xor1“成对储存”的性质
                now=pre[now]; //维护前向边last,前向点pre
            }
        }
    }
    
    int main(){
        reads(n),reads(m); memset(head,-1,sizeof(head));
        S=2*n+2,T=2*n+3; add(2*n+1,0,m,0); //ss=0,tt=2*n+1
        for(int i=1,x;i<=n;i++) reads(x),add(0,i,inf,0),
            add(i+n,2*n+1,inf,0),add(S,i+n,x,0),add(i,T,x,0);
        for(int i=1,x;i<=n;i++) for(int j=i+1;j<=n;j++)
         { reads(x); if(~x) add(i+n,j,inf,x); }
        mcmf(); printf("%d
    ",minc); return 0; //↑↑注意建图方式
    }
    【p4553】80人环游地球 //有上下界的费用流【两个源点、汇点】

                                             ——时间划过风的轨迹,那个少年,还在等你

  • 相关阅读:
    Practical .NET2 and C#2 翻译样章
    Resume
    Double Dispatch And Visitor Pattern
    Separate Contract from Implementation
    Kerberos简介
    责任分离的思想 oo dp orm aop
    Resources on Debugging/Tracing WPF
    沿着“重用”我们一路走来——SA、OO(DP)、Component、SOA、AOP
    Enterprise Test Driven Develop
    How does ElementName Binding work – Part 2 BindingExpression
  • 原文地址:https://www.cnblogs.com/FloraLOVERyuuji/p/10507252.html
Copyright © 2020-2023  润新知