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

最小费用最大流

#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人环游地球 //有上下界的费用流【两个源点、汇点】

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

原文地址:https://www.cnblogs.com/FloraLOVERyuuji/p/10507252.html