网络流24题解题总结(更新中)

目录

前言

从今天(2018.1.19)起,本人要在这篇文章中持续更新网络流24题的解法。
注意,本人这里贴的做法和代码都以洛谷上的题目版本为准,可能与其他OJ的版本略有出入,所以请自行判断。
为了讨论方便,我们约定以下符号:
S,T:源点和汇点。
(u,v,maxf,c):表示从uv连一条容量为maxf,费用为c的边。有时费用为0时省略c
<u,v,minf,maxf,c>:表示从uv连一条流量下界为minf,上界为maxf,费用为c的边。有时费用为0时省略c
那么,现在就开始吧。

最大流问题

飞行员配对方案问题

更新时间: 2018.1.19
测试地址:飞行员配对方案问题
做法:本题需要用到最大流。
这个题目是很经典的二分图最大匹配的模型了,用匈牙利算法也可以做,但既然出现在网络流24题里,那么就用最大流做即可。具体做法应该不用多说,从S到所有外籍飞行员连边,从所有英国飞行员到T连边,再在可以配合的飞行员之间连边(从外籍到英国),容量都为1,然后跑最大流即可。
至于输出方案,显然可知,在我们连的正向边中满流的边就是匹配边,所以一个循环搞定。
以下是本人代码:

#include <bits/stdc++.h>
#define inf 1000000000
using namespace std;
int m,n,a,b,first[210]={0},tot=1,lvl[210];
struct edge {int v,next,f;} e[100010];
queue <int> Q;

void insert(int a,int b,int f)
{
    e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot;
    e[++tot].v=a,e[tot].next=first[b],e[tot].f=0,first[b]=tot;
}

bool makelevel()
{
    lvl[0]=0;
    for(int i=1;i<=m+n+1;i++)
        lvl[i]=-1;
    Q.push(0);
    while(!Q.empty())
    {
        int v=Q.front();Q.pop();
        for(int i=first[v];i;i=e[i].next)
            if (e[i].f&&lvl[e[i].v]==-1)
            {
                lvl[e[i].v]=lvl[v]+1;
                Q.push(e[i].v);
            }
    }
    return lvl[m+n+1]!=-1;
}

int maxflow(int v,int maxf)
{
    if (v==m+n+1) return maxf;
    int ret=0,f;
    for(int i=first[v];i;i=e[i].next)
        if (e[i].f&&lvl[e[i].v]==lvl[v]+1)
        {
            f=maxflow(e[i].v,min(e[i].f,maxf-ret));
            e[i].f-=f;
            e[i^1].f+=f;
            ret+=f;
            if (ret==maxf) break;
        }
    return ret;
}

int dinic()
{
    int maxf=0;
    while(makelevel()) maxf+=maxflow(0,inf);
    return maxf;
}

int main()
{
    scanf("%d%d",&m,&n);
    for(int i=1;i<=m;i++) insert(0,i,1);
    for(int i=1;i<=n;i++) insert(m+i,m+n+1,1);
    while(scanf("%d%d",&a,&b)&&a!=-1) insert(a,b,1);

    printf("%d\n",dinic());
    for(int i=1;i<=m;i++)
        for(int j=first[i];j;j=e[j].next)
            if (!e[j].f&&j%2==0) printf("%d %d\n",i,e[j].v);

    return 0;
}

最小路径覆盖问题

更新时间: 2018.1.20
测试地址:最小路径覆盖问题
做法:本题需要用到最大流。
求有向无环图的最小路径覆盖,可以转化为二分图最大匹配的模型求解。将每个点拆成两个点xi,yi,然后如果原图存在有向边i->j,则在二分图中连yi->xj。注意到,对于该二分图的任意一个匹配,将所有xi,yi缩成一个点后,就等价于一个路径覆盖,而且覆盖的路径条数=点数匹配数。那么显然,当匹配数最大时,覆盖的路径条数自然就最小,那么我们就得到了一个最小的路径覆盖。因此只用对上面构造出的二分图求最大匹配,可以用最大流解决。输出方案应该也很容易,只需要从路径的开头沿着匹配边走就可以了。
以下是本人代码:

#include<bits/stdc++.h>
#define inf 1000000000
using namespace std;
int n,m,first[310]={0},tot=1,lvl[310]={0},path[160]={0};
struct edge {int v,next,f;} e[100010];
queue <int> Q;
bool vis[160]={0},in[160]={0};

void insert(int a,int b,int f)
{
    e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot;
    e[++tot].v=a,e[tot].next=first[b],e[tot].f=0,first[b]=tot;
}

bool makelevel()
{
    lvl[0]=0;
    for(int i=1;i<=2*n+1;i++)
        lvl[i]=-1;
    Q.push(0);
    while(!Q.empty())
    {
        int v=Q.front();Q.pop();
        for(int i=first[v];i;i=e[i].next)
            if (e[i].f&&lvl[e[i].v]==-1)
            {
                lvl[e[i].v]=lvl[v]+1;
                Q.push(e[i].v);
            }
    }
    return lvl[2*n+1]!=-1;
}

int maxflow(int v,int maxf)
{
    if (v==2*n+1) return maxf;
    int ret=0,f;
    for(int i=first[v];i;i=e[i].next)
        if (e[i].f&&lvl[e[i].v]==lvl[v]+1)
        {
            f=maxflow(e[i].v,min(e[i].f,maxf-ret));
            e[i].f-=f;
            e[i^1].f+=f;
            ret+=f;
            if (ret==maxf) break;
        }
    return ret;
}

int dinic()
{
    int maxf=0;
    while(makelevel()) maxf+=maxflow(0,inf);
    return maxf;
}

void output()
{
    for(int i=1;i<=n;i++)
        for(int j=first[2*i-1];j;j=e[j].next)
            if (j%2==0&&!e[j].f) in[e[j].v/2]=1;
    for(int i=1;i<=n;i++)
        if (!in[i])
        {
            path[0]=0;
            int x=2*i-1;
            bool flag=1;
            while(flag)
            {
                flag=0;
                path[++path[0]]=(x+1)/2;
                vis[(x+1)/2]=1;
                for(int j=first[x];j;j=e[j].next)
                    if (j%2==0&&!e[j].f) {x=e[j].v-1;flag=1;break;}
            }
            for(int j=path[0];j>=1;j--) printf("%d ",path[j]);
            printf("\n");
        }
    for(int i=first[0];i;i=e[i].next)
        if (!vis[(e[i].v+1)/2]) printf("%d\n",(e[i].v+1)/2);
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        insert(2*b-1,2*a,1);
    }
    for(int i=1;i<=n;i++)
    {
        insert(0,2*i-1,1);
        insert(2*i,2*n+1,1);
    }

    int ans=n-dinic();
    output();
    printf("%d",ans);

    return 0;
}

魔术球问题

更新时间: 2018.1.20
测试地址:魔术球问题
做法:本题需要用到最大流。
我们想办法把该题化成我们见过的模型解决。我们很快想到,需要从小到大枚举球的个数,然后化为判定性问题:能不能用不超过n根柱子放下这些球?可以看出,球ij(i<j)如果能放在一起,那么就相当于存在有向边i->j,那么一根柱子就相当于一条路径,因此只需对这样构造出的有向图求一个最小路径覆盖,如果路径数不超过n就表示能放。求最小路径覆盖我们就很熟悉了,分析详见上面一题。
以下是本人代码:

#include<bits/stdc++.h>
#define inf 1000000000
#define s 0
#define t 4009
using namespace std;
int n,m,first[4010]={0},tot=1,lvl[4010]={0},saved=0;
struct edge {int v,next,f;} e[400010];
queue <int> Q;
bool vis[2010]={0},in[2010]={0};

void insert(int a,int b,int f)
{
    e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot;
    e[++tot].v=a,e[tot].next=first[b],e[tot].f=0,first[b]=tot;
}

bool makelevel()
{
    lvl[s]=0;
    for(int i=1;i<=m;i++)
        lvl[i]=-1;
    lvl[t]=-1;
    Q.push(0);
    while(!Q.empty())
    {
        int v=Q.front();Q.pop();
        for(int i=first[v];i;i=e[i].next)
            if (e[i].f&&(e[i].v<=m||e[i].v==t)&&lvl[e[i].v]==-1)
            {
                lvl[e[i].v]=lvl[v]+1;
                Q.push(e[i].v);
            }
    }
    return lvl[t]!=-1;
}

int maxflow(int v,int maxf)
{
    if (v==t) return maxf;
    int ret=0,f;
    for(int i=first[v];i;i=e[i].next)
        if (e[i].f&&(e[i].v<=m||e[i].v==t)&&lvl[e[i].v]==lvl[v]+1)
        {
            f=maxflow(e[i].v,min(e[i].f,maxf-ret));
            e[i].f-=f;
            e[i^1].f+=f;
            ret+=f;
            if (ret==maxf) break;
        }
    return ret;
}

int dinic()
{
    int maxf=saved;
    while(makelevel()) maxf+=maxflow(0,inf);
    saved=maxf;
    return maxf;
}

void output()
{
    for(int i=1;i<=m/2;i++)
        for(int j=first[2*i-1];j;j=e[j].next)
            if (j%2==0&&!e[j].f) in[e[j].v/2]=1;
    for(int i=1;i<=m/2;i++)
        if (!in[i]&&!vis[i])
        {
            int x=2*i-1;
            bool flag=1;
            while(flag)
            {
                flag=0;
                printf("%d ",(x+1)/2);
                vis[(x+1)/2]=1;
                for(int j=first[x];j;j=e[j].next)
                    if (j%2==0&&!e[j].f) {x=e[j].v-1;flag=1;break;}
            }
            printf("\n");
        }
    for(int i=first[0];i;i=e[i].next)
        if (!vis[(e[i].v+1)/2]&&e[i].v<=m) printf("%d\n",(e[i].v+1)/2);
}

bool check(int now)
{
    m=2*now;
    insert(s,2*now-1,1);
    insert(2*now,t,1);
    int j=1;
    for(int i=1;i<now;i++)
    {
        while(j*j<i+now) j++;
        if (j*j==i+now) insert(2*i-1,2*now,1);
    }
    int ans=now-dinic();
    return ans<=n;
}

int main()
{
    scanf("%d",&n);

    int i=1;
    while(check(i)) i++;

    i--;
    m=2*i;
    saved=0;
    for(int i=2;i<=tot;i+=2)
        e[i].f=1,e[i+1].f=0;
    dinic();
    printf("%d\n",i);
    output();

    return 0;
}

圆桌问题

更新时间: 2018.1.20
测试地址:圆桌问题
做法:本题需要用到最大流。
本题是一个经典的二分图多重匹配的模型,显然对于每个单位i连接(S,i,ri),对于每个餐桌j连接(j,T,cj),又因为每个餐桌不能坐两个或者以上的同一个单位的代表,所以对于单位i和餐桌j之间应该连接(i,j,1),建完之后跑最大流即可。输出方案的方法和前面的二分图最大匹配差不多,不同的是这里有无解的情况,如果最大流不等于所有单位代表人数总和的话,显然是无解的,其余的输出方法详见代码。
以下是本人代码:

#include <bits/stdc++.h>
#define s 0
#define t m+n+1
#define inf 1000000000
using namespace std;
int m,n,sum=0,first[510]={0},tot=1,lvl[510];
struct edge {int v,next,f;} e[100010];
queue <int> Q;

void insert(int a,int b,int f)
{
    e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot;
    e[++tot].v=a,e[tot].next=first[b],e[tot].f=0,first[b]=tot;
}

bool makelevel()
{
    lvl[s]=0;
    for(int i=1;i<=t;i++) lvl[i]=-1;
    Q.push(s);
    while(!Q.empty())
    {
        int v=Q.front();Q.pop();
        for(int i=first[v];i;i=e[i].next)
            if (e[i].f&&lvl[e[i].v]==-1)
            {
                lvl[e[i].v]=lvl[v]+1;
                Q.push(e[i].v);
            }
    }
    return lvl[t]!=-1;
}

int maxflow(int v,int maxf)
{
    if (v==t) return maxf;
    int ret=0,f;
    for(int i=first[v];i;i=e[i].next)
        if (e[i].f&&lvl[e[i].v]==lvl[v]+1)
        {
            f=maxflow(e[i].v,min(e[i].f,maxf-ret));
            e[i].f-=f;
            e[i^1].f+=f;
            ret+=f;
            if (ret==maxf) break;
        }
    return ret;
}

int dinic()
{
    int maxf=0;
    while(makelevel()) maxf+=maxflow(s,inf);
    return maxf;
}

void output()
{
    for(int i=1;i<=m;i++)
    {
        for(int j=first[i];j;j=e[j].next)
            if (j%2==0&&!e[j].f) printf("%d ",e[j].v-m);
        printf("\n");
    }
}

int main()
{
    scanf("%d%d",&m,&n);
    for(int i=1;i<=m;i++)
    {
        int a;
        scanf("%d",&a);
        sum+=a;
        insert(s,i,a);
    }
    for(int i=1;i<=n;i++)
    {
        int a;
        scanf("%d",&a);
        insert(m+i,t,a);
    }
    for(int i=1;i<=m;i++)
        for(int j=1;j<=n;j++)
            insert(i,m+j,1);

    if (dinic()!=sum) printf("0");
    else
    {
        printf("1\n");
        output();
    }

    return 0;
}

最长不下降子序列问题

更新时间: 2018.1.21
测试地址:最长不下降子序列问题
做法:本题需要用到DP+最大流。
对于第一问,我们直接O(n2)DP求出答案即可,将答案记为K
对于第二问,令f(i)为以第i个元素结尾的最长不下降子序列的长度,这个东西在上一步DP时就已经求出来了,接下来我们这样建图:如果两点i,j满足关系:i<jaiajf(i)+1=f(j),则连有向边i->j。注意到,从任意一个f(i)=1的点,沿这些有向边走到一个f(i)=K的点,中间的路径上经过的点就是一个长为K的最长不下降子序列,那么问题就转化为:有若干个起点和终点,求最多能有多少条不相交的从起点到终点的路径?这个模型就可以用网络流的方法解决了。
首先,因为每个点只能出现一次,所以有“点流量”的限制,这里每个点的限制都是1。一般遇到这种情况,我们可以将一个点拆成两个点u,v,并将原点的入边都连到u,将原点的出边都连到v,然后连接(u,v,maxf),这里显然maxf=1,这样就可以解决点流量限制的问题。剩下的部分应该挺容易想出来了,对于所有f(i)=1的点,连接(S,ui,1),对于所有f(i)=K的点,连接(vi,T,1),对于原图中所有的有向边i->j,对应连接(vi,uj,1),然后对这个网络跑一次最大流就是第二问的答案。
对于第三问,第1和第n个元素可以出现多次,那么我们只需将原网络中的(S,u1,1),(u1,v1,1),(un,vn,1),(vn,T,1)四条边的容量改为inf,然后再跑一次最大流就是第三问的答案。
以下是本人代码:

#include<bits/stdc++.h>
#define s 0
#define t 2*n+1
#define inf 1000000000
using namespace std;
int n,a[510],f[510],mx=0,first[1010]={0},tot=1,lvl[1010];
struct edge {int v,next,f;} e[200010];
queue <int> Q;

void insert(int a,int b,int f)
{
    e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot;
    e[++tot].v=a,e[tot].next=first[b],e[tot].f=0,first[b]=tot;
}

bool makelevel()
{
    lvl[s]=0;
    for(int i=1;i<=t;i++) lvl[i]=-1;
    Q.push(s);
    while(!Q.empty())
    {
        int v=Q.front();Q.pop();
        for(int i=first[v];i;i=e[i].next)
            if (e[i].f&&lvl[e[i].v]==-1)
            {
                lvl[e[i].v]=lvl[v]+1;
                Q.push(e[i].v);
            }
    }
    return lvl[t]!=-1;
}

int maxflow(int v,int maxf)
{
    if (v==t) return maxf;
    int ret=0,f;
    for(int i=first[v];i;i=e[i].next)
        if (e[i].f&&lvl[e[i].v]==lvl[v]+1)
        {
            f=maxflow(e[i].v,min(e[i].f,maxf-ret));
            e[i].f-=f;
            e[i^1].f+=f;
            ret+=f;
            if (ret==maxf) break;
        }
    return ret;
}

int dinic()
{
    int maxf=0;
    while(makelevel()) maxf+=maxflow(0,inf);
    return maxf;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        f[i]=1;
        for(int j=1;j<i;j++)
            if (a[j]<=a[i]) f[i]=max(f[i],f[j]+1);
        for(int j=1;j<i;j++)
            if (a[j]<=a[i]&&f[i]==f[j]+1)
                insert(2*j,2*i-1,1);
        mx=max(mx,f[i]);
    }
    for(int i=1;i<=n;i++)
    {
        insert(2*i-1,2*i,1);
        if (f[i]==1) insert(s,2*i-1,1);
        if (f[i]==mx) insert(2*i,t,1);
    }

    printf("%d\n",mx);
    printf("%d\n",dinic());

    for(int i=2;i<=tot;i+=2)
        e[i].f=1,e[i+1].f=0;
    for(int i=first[s];i;i=e[i].next)
        if (i%2==0&&e[i].v==1) {e[i].f=inf;break;}
    for(int i=first[1];i;i=e[i].next)
        if (i%2==0&&e[i].v==2) {e[i].f=inf;break;}
    for(int i=first[2*n-1];i;i=e[i].next)
        if (i%2==0&&e[i].v==2*n) {e[i].f=inf;break;}
    for(int i=first[2*n];i;i=e[i].next)
        if (i%2==0&&e[i].v==t) {e[i].f=inf;break;}

    printf("%d",dinic());

    return 0;
}

试题库问题

更新时间: 2018.1.21
测试地址:试题库问题
做法:本题需要用到最大流。
本题也是经典的二分图多重匹配的模型,不过不像圆桌问题那样可以随便匹配了,每个类型只对应一部分题目。那么显然,对于所有类型i,连接(S,i,pi),对于所有题目i,连接(i,T,1)(因为每道题目只能用一次),如果题目j包含类型i,那么连接(i,j,1),对这个网络跑一遍最大流,然后照和圆桌问题类似的输出方案的方法输出即可。
以下是本人代码:

#include<bits/stdc++.h>
#define s 0
#define t m+n+1
#define inf 1000000000
using namespace std;
int m,n,sum=0,first[1510]={0},tot=1,lvl[1510];
struct edge {int v,next,f;} e[200010];
queue <int> Q;

void insert(int a,int b,int f)
{
    e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot;
    e[++tot].v=a,e[tot].next=first[b],e[tot].f=0,first[b]=tot;
}

bool makelevel()
{
    lvl[s]=0;
    for(int i=1;i<=t;i++) lvl[i]=-1;
    Q.push(s);
    while(!Q.empty())
    {
        int v=Q.front();Q.pop();
        for(int i=first[v];i;i=e[i].next)
            if (e[i].f&&lvl[e[i].v]==-1)
            {
                lvl[e[i].v]=lvl[v]+1;
                Q.push(e[i].v);
            }
    }
    return lvl[t]!=-1;
}

int maxflow(int v,int maxf)
{
    if (v==t) return maxf;
    int ret=0,f;
    for(int i=first[v];i;i=e[i].next)
        if (e[i].f&&lvl[e[i].v]==lvl[v]+1)
        {
            f=maxflow(e[i].v,min(e[i].f,maxf-ret));
            e[i].f-=f;
            e[i^1].f+=f;
            ret+=f;
            if (ret==maxf) break;
        }
    return ret;
}

int dinic()
{
    int maxf=0;
    while(makelevel()) maxf+=maxflow(0,inf);
    return maxf;
}

void output()
{
    for(int i=1;i<=m;i++)
    {
        printf("%d:",i);
        for(int j=first[i];j;j=e[j].next)
            if (!e[j].f&&j%2==0&&e[j].v>m) printf(" %d",e[j].v-m);
        printf("\n");
    }
}

int main()
{
    scanf("%d%d",&m,&n);
    for(int i=1;i<=m;i++)
    {
        int p;
        scanf("%d",&p);
        insert(s,i,p);
        sum+=p;
    }
    for(int i=1;i<=n;i++)
    {
        int p,a;
        scanf("%d",&p);
        while(p--)
        {
            scanf("%d",&a);
            insert(a,m+i,1);
        }
        insert(m+i,t,1);
    }

    if (dinic()!=sum) printf("No Solution!");
    else output();

    return 0;
}

星际转移问题

更新时间: 2018.1.24
测试地址:星际转移问题
做法:本题需要用到最大流。
由于最终的天数不确定,所以我们枚举答案,转化为判定性问题:ans天内人能不能全部到达月球?对于每一天,建n+2个点,表示这一天的地球、n个太空站和月球,然后建模就比较容易了:
对于每一天的地球,连接(S,earthi,inf)
对于每一天的月球,连接(mooni,T,inf)
对于所有飞船,如果前一天在点x,当天在点y,那么连接(xi1,yi,h)
对于每个点x,连接(xi1,xi,inf)(人留在原地等的情况)。
这样一来,只要当天的最大流达到了k,就说明当天可以有k个人到达月球了。因为我们是从小到大枚举答案,所以我们可以在每一次做完的残余网络上再加新边再跑,这样会跑的快些(大概吧)。
最后还有一个问题,判断无解的问题。网上有人直接估算可能达到的最大天数,这样有WA或TLE的风险,最稳的方法还是并查集判断连通性,详见代码。
以下是本人代码:

#include<bits/stdc++.h>
#define inf 1000000000
#define s 0
#define t 10009
using namespace std;
int n,m,k,first[20010]={0},tot=1,lvl[20010]={0},saved=0,tim=1;
int h[25],r[25],p[25][25],fa[25];
struct edge {int v,next,f;} e[1000010];
queue <int> Q;

void insert(int a,int b,int f)
{
    e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot;
    e[++tot].v=a,e[tot].next=first[b],e[tot].f=0,first[b]=tot;
}

bool makelevel()
{
    lvl[s]=0;
    for(int i=1;i<=(tim+1)*(n+2);i++)
        lvl[i]=-1;
    lvl[t]=-1;
    Q.push(0);
    while(!Q.empty())
    {
        int v=Q.front();Q.pop();
        for(int i=first[v];i;i=e[i].next)
            if (e[i].f&&lvl[e[i].v]==-1)
            {
                lvl[e[i].v]=lvl[v]+1;
                Q.push(e[i].v);
            }
    }
    return lvl[t]!=-1;
}

int maxflow(int v,int maxf)
{
    if (v==t) return maxf;
    int ret=0,f;
    for(int i=first[v];i;i=e[i].next)
        if (e[i].f&&lvl[e[i].v]==lvl[v]+1)
        {
            f=maxflow(e[i].v,min(e[i].f,maxf-ret));
            e[i].f-=f;
            e[i^1].f+=f;
            ret+=f;
            if (ret==maxf) break;
        }
    return ret;
}

int dinic()
{
    int maxf=saved;
    while(makelevel()) maxf+=maxflow(0,inf);
    saved=maxf;
    return maxf;
}

bool check()
{
    for(int i=0;i<=n+1;i++)
        insert((tim-1)*(n+2)+i+1,tim*(n+2)+i+1,inf);
    insert(s,(tim-1)*(n+2)+1,inf);
    insert(tim*(n+2)+n+2,t,inf);
    for(int i=1;i<=m;i++)
    {
        int x=p[i][tim%r[i]],y=p[i][(tim-1+r[i])%r[i]];
        insert((tim-1)*(n+2)+y+1,tim*(n+2)+x+1,h[i]);
    }
    dinic();
    return saved>=k;
}

int ufs_find(int x)
{
    int r=x,i=x,j;
    while(fa[r]!=r) r=fa[r];
    while(i!=r) j=fa[i],fa[i]=r,i=j;
    return r;
}

void ufs_merge(int x,int y)
{
    int fx=ufs_find(x),fy=ufs_find(y);
    if (fx!=fy) fa[fx]=fy; 
}

int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i=0;i<=n+1;i++) fa[i]=i;

    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&h[i],&r[i]);
        for(int j=0;j<r[i];j++)
        {
            scanf("%d",&p[i][j]);
            if (p[i][j]==-1) p[i][j]=n+1;
            if (j>0) ufs_merge(p[i][j],p[i][j-1]);
        }
    }

    if (ufs_find(0)!=ufs_find(n+1)) {printf("0");return 0;} 

    while(!check()) tim++;
    printf("%d",tim);

    return 0;
}

最小割问题

太空飞行计划问题

更新时间: 2018.1.20
测试地址:太空飞行计划问题
做法:本题需要用到最小割。
本题是一个经典的求最大权闭合子图的模型。所谓闭合子图,是指求一个顶点集,使得该顶点集中任意一个点的出边都指向该顶点集中的点,而最大权闭合子图就是在所有闭合子图中求一个点权和最大的。因此,最大权闭合子图的模型常用来解决一些有依赖关系的选取问题,构图方法是:如果选a就必须选b,那么连接a->b。经过一些证明(具体证明看这里),可以得到一个用最小割的思想求解该问题的模型:
对于原图中所有的边u->v,连接(u,v,inf),接下来对于所有正权点u,连接(S,u,valueu),对于所有负权点v,连接(v,T,|valuev|)。然后对于这个网络求最小割(也就等同于求最大流),记作f,在最小割分成的两个顶点集中,S所在的集合为所求,而最大权和就是所有正权点权值和减去f
至于输出方案,求完最大流之后,从S通过残余网络能到达的所有点就是所求的答案,虽然我不太会证明割边一定满流,有待学习。
以下是本人代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll inf=1000000000,sum=0;
int m,n,first[110]={0},tot=1,lvl[110]={0};
struct edge {int v,next;ll f;} e[100010];
queue <int> Q;

int read(bool &flag)
{
    char c;
    int s=0;
    c=getchar();
    while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9') s=s*10+c-'0',c=getchar();
    flag=(c=='\n'||c=='\r');
    return s;
}

void insert(int a,int b,ll f)
{
    e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot;
    e[++tot].v=a,e[tot].next=first[b],e[tot].f=0,first[b]=tot;
}

bool makelevel()
{
    lvl[0]=0;
    for(int i=1;i<=m+n+1;i++)
        lvl[i]=-1;
    Q.push(0);
    while(!Q.empty())
    {
        int v=Q.front();Q.pop();
        for(int i=first[v];i;i=e[i].next)
            if (e[i].f&&lvl[e[i].v]==-1)
            {
                lvl[e[i].v]=lvl[v]+1;
                Q.push(e[i].v);
            }
    }
    return lvl[m+n+1]!=-1;
}

ll maxflow(int v,ll maxf)
{
    if (v==m+n+1) return maxf;
    ll ret=0,f;
    for(int i=first[v];i;i=e[i].next)
        if (e[i].f&&lvl[e[i].v]==lvl[v]+1)
        {
            f=maxflow(e[i].v,min(e[i].f,maxf-ret));
            e[i].f-=f;
            e[i^1].f+=f;
            ret+=f;
            if (ret==maxf) break;
        }
    return ret;
}

ll dinic()
{
    ll maxf=0;
    while(makelevel()) maxf+=maxflow(0,inf);
    return maxf;
}

int main()
{
    inf*=inf;

    scanf("%d%d",&m,&n);
    for(int i=1;i<=m;i++)
    {
        ll f;
        bool flag=0;
        scanf("%lld",&f);
        sum+=f;
        insert(0,i,f);
        while(true)
        {
            int s=read(flag);
            insert(i,m+s,inf);
            if (flag) break;
        }
    }
    for(int i=1;i<=n;i++)
    {
        ll f;
        scanf("%lld",&f);
        insert(m+i,m+n+1,f);
    }

    ll ans=sum-dinic();
    for(int i=1;i<=m;i++)
        if (lvl[i]!=-1) printf("%d ",i);
    printf("\n");
    for(int i=1;i<=n;i++)
        if (lvl[i+m]!=-1) printf("%d ",i);
    printf("\n%lld",ans);

    return 0;
}

方格取数问题

更新时间: 2018.1.21
测试地址:方格取数问题
做法:本题需要用到最小割。
将每个格子看做点,然后将每个点和上下左右四个点连边,显然地,因为该图不存在奇环,所以网格图是一个二分图。那么问题就转化为求二分图的最大点权和独立集,这时我们想办法将其转化为我们更加熟悉的模型解决。
我们试探性地往图中加入源点和汇点,然后对于二分图的其中一部分,从S到所有该部分中的点连边,从所有另一部分中的点到T连边,边权为该点对应的格子上数的值,接下来将原二分图中所有的边权设为inf,方向设为从连接S的点指向连接T的点,我们来看看这个图有什么性质。可以看出,每一条从ST的路径都对应了一对不能同时取的点,所以这些路径都不能存在,因此我们需要去掉一些边使得从S不能到达T,这里我们只会去掉边权不为inf的边,因此每去掉一条边都相当于放弃了与这条边相连的那个点。而我们又要使得剩下的点的点权和最大,也就意味着剩下的边权不为inf的边权和最大,也就是删掉的边的边权和最小,这显然就是一个最小割了。所以我们按照上述方法建一个网络,容量即为边权,然后跑一遍最大流,因为最大流等于最小割,所以用所有数的和减去得到的结果就是最后的答案。
以下是本人代码:

#include<bits/stdc++.h>
#define s 0
#define t m*n+1
#define inf 1000000000
using namespace std;
int m,n,sum=0,first[10010]={0},tot=1,lvl[10010];
struct edge {int v,next,f;} e[200010];
queue <int> Q;

void insert(int a,int b,int f)
{
    e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot;
    e[++tot].v=a,e[tot].next=first[b],e[tot].f=0,first[b]=tot;
}

bool makelevel()
{
    lvl[s]=0;
    for(int i=1;i<=t;i++) lvl[i]=-1;
    Q.push(s);
    while(!Q.empty())
    {
        int v=Q.front();Q.pop();
        for(int i=first[v];i;i=e[i].next)
            if (e[i].f&&lvl[e[i].v]==-1)
            {
                lvl[e[i].v]=lvl[v]+1;
                Q.push(e[i].v);
            }
    }
    return lvl[t]!=-1;
}

int maxflow(int v,int maxf)
{
    if (v==t) return maxf;
    int ret=0,f;
    for(int i=first[v];i;i=e[i].next)
        if (e[i].f&&lvl[e[i].v]==lvl[v]+1)
        {
            f=maxflow(e[i].v,min(e[i].f,maxf-ret));
            e[i].f-=f;
            e[i^1].f+=f;
            ret+=f;
            if (ret==maxf) break;
        }
    return ret;
}

int dinic()
{
    int maxf=0;
    while(makelevel()) maxf+=maxflow(0,inf);
    return maxf;
}

int main()
{
    scanf("%d%d",&m,&n);
    for(int i=1;i<=m;i++)
        for(int j=1;j<=n;j++)
        {
            int a;
            scanf("%d",&a);
            sum+=a;
            if ((i+j)%2==0)
            {
                if (i>1) insert((i-1)*n+j,(i-2)*n+j,inf);
                if (i<m) insert((i-1)*n+j,i*n+j,inf);
                if (j>1) insert((i-1)*n+j,(i-1)*n+j-1,inf);
                if (j<n) insert((i-1)*n+j,(i-1)*n+j+1,inf);
                insert(s,(i-1)*n+j,a);
            }
            else insert((i-1)*n+j,t,a);
        }

    printf("%d",sum-dinic());

    return 0;
}

费用流问题

餐巾计划问题

更新时间: 2018.1.19
测试地址:餐巾计划问题
做法:本题需要用到费用流。
将本题的模型简化一下,可以知道第i天的入度来源于:新购买的餐巾,前一天没用完的干净餐巾,刚快洗或慢洗完的餐巾,而出度都指向第i+m天(快洗)和第i+n天(慢洗),而且要求点流量必须大于等于ri。那么显然我们把代表一天的点拆成两个点u,v,并连接<u,v,ri,inf>。根据上下界网络流的解法(实际上是看大佬题解抄来的),这条边等价于(S,v,ri)+(u,T,ri)+(u,v,inf)。剩下的建模就很简单了,只需要连(S,u1,inf,p)(相当于把所有要买的餐巾一上来先买好),(vi,ui+m,inf,f)(快洗)和(vi,ui+n,inf,s)(慢洗),然后跑个正常的费用流即可。
以下是本人代码:

#include <bits/stdc++.h>
#define ll long long
#define inf 1000000000
using namespace std;
int N,first[5010]={0},tot=1,laste[5010],last[5010];
ll p,m,f,n,s,dis[5010];
bool vis[5010]={0};
struct edge {ll v,f,c,next;} e[200010];
queue <int> Q;

void insert(int a,int b,ll f,ll c)
{
    e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,e[tot].c=c,first[a]=tot;
    e[++tot].v=a,e[tot].next=first[b],e[tot].f=0,e[tot].c=-c,first[b]=tot;
}

bool spfa()
{
    ll s;
    while(!Q.empty()) Q.pop();
    Q.push(0);
    memset(vis,0,sizeof(vis));
    dis[0]=0;vis[0]=1;
    for(int i=1;i<=2*N+1;i++)
        dis[i]=inf;
    while(!Q.empty())
    {
        int v=Q.front();Q.pop();
        for(int i=first[v];i;i=e[i].next)
            if (e[i].f&&dis[v]+e[i].c<dis[e[i].v])
            {
                dis[e[i].v]=dis[v]+e[i].c;
                laste[e[i].v]=i;
                last[e[i].v]=v;
                if (!vis[e[i].v]) Q.push(e[i].v),vis[e[i].v]=1;
            }
        vis[v]=0;
    }
    return dis[2*N+1]!=inf;
}

ll mincost()
{
    ll ans=0;
    while(spfa())
    {
        int x=2*N+1;
        ll minf=inf;
        while(x) minf=min(minf,e[laste[x]].f),x=last[x];
        x=2*N+1;
        while(x) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x];
        ans+=minf*dis[2*N+1];
    }
    return ans;
}

int main()
{
    scanf("%d",&N);
    for(int i=1;i<=N;i++)
    {
        ll r;
        scanf("%lld",&r);
        insert(0,2*i,r,0);
        insert(2*i-1,2*N+1,r,0);
        insert(2*i-1,2*i,inf,0);
    }
    scanf("%lld%lld%lld%lld%lld",&p,&m,&f,&n,&s);

    insert(0,1,inf,p);
    for(int i=1;i<=N;i++)
    {
        if (i<N) insert(2*i-1,2*i+1,inf,0);
        if (i+m<=N) insert(2*i,2*(i+m)-1,inf,f);
        if (i+n<=N) insert(2*i,2*(i+n)-1,inf,s);
    }

    printf("%lld",mincost());

    return 0;
}

航空路线问题

更新时间: 2018.1.23
测试地址:航空路线问题
做法:本题需要用到费用流。
首先,题目求的是一个环,包含从西向东和从东向西两条不相交(除起点和终点)的路径,显然可以看做两条从西向东的两条不相交(除起点和终点)路径来求。因为每个城市只能经过一次,所以有点流量限制,因此我们把一个点拆成两个点ui,vi,并在中间连接(ui,vi,maxf,1),显然对于除起点和终点的其他城市,点流量限制为1,否则为2。其他部分就比较简单了,先连接(S,u1,2)(vn,T,inf),再将原图的无向边改成从西向东的有向边i->j,因为我们拆了点,所以实际连的是(vi,uj,inf)。再然后,因为要求经过的点最多,所以跑最大费用最大流即可,只需将SPFA中求最短路改成求最长路即可。特殊地,如果最大流小于2则无解。至于输出方案,应该不难,但是要注意细节。
以下是本人代码:

#include<bits/stdc++.h>
#define s 0
#define t 2*n+1
#define inf 1000000000
using namespace std;
int n,m,first[210]={0},tot=1,dis[210],last[210],laste[210],maxf,path[210];
char name[110][20],names[20];
struct edge {int v,next,f,c;} e[100010];
bool vis[210]={0};
queue <int> Q;

void insert(int a,int b,int f,int c)
{
    e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,e[tot].c=c,first[a]=tot;
    e[++tot].v=a,e[tot].next=first[b],e[tot].f=0,e[tot].c=-c,first[b]=tot;
}

bool spfa()
{
    dis[s]=0,vis[s]=1;
    for(int i=1;i<=t;i++)
        dis[i]=-inf;
    Q.push(s);
    while(!Q.empty())
    {
        int v=Q.front();Q.pop();
        for(int i=first[v];i;i=e[i].next)
            if (e[i].f&&dis[e[i].v]<dis[v]+e[i].c)
            {
                dis[e[i].v]=dis[v]+e[i].c;
                last[e[i].v]=v;
                laste[e[i].v]=i;
                if (!vis[e[i].v]) vis[e[i].v]=1,Q.push(e[i].v);
            }
        vis[v]=0;
    }
    return dis[t]!=-inf;
}

int maxcost()
{
    int ans=0;
    maxf=0;
    while(spfa())
    {
        int x=t,minf=inf;
        while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x];
        x=t;
        while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x];
        ans+=dis[t]*minf;
        maxf+=minf;
    }
    return ans;
}

void output()
{
    int x,i;
    bool flag=0;
    printf("%s\n",name[1]);
    for(i=first[2];i;i=e[i].next)
        if ((e[i].f==inf-1||e[i].f==inf-2)&&i%2==0) break;
    x=e[i].v;
    while(x!=2*n-1)
    {
        printf("%s\n",name[(x+1)/2]);
        x++;
        for(int j=first[x];j;j=e[j].next)
            if (e[j].f==inf-1&&j%2==0) {x=e[j].v;break;}
    }
    printf("%s\n",name[n]);
    if (e[i].f!=inf-2)
    {
        i=e[i].next;
        for(;i;i=e[i].next)
            if (e[i].f==inf-1&&i%2==0) break;
        x=e[i].v;
        path[0]=0;
        while(x!=2*n-1)
        {
            path[++path[0]]=(x+1)/2;
            x++;
            for(int j=first[x];j;j=e[j].next)
                if (e[j].f==inf-1&&j%2==0) {x=e[j].v;break;}
        }
        for(i=path[0];i>=1;i--) printf("%s\n",name[path[i]]);
    }
    printf("%s",name[1]);
}

int find()
{
    for(int i=1;i<=n;i++)
    {
        bool flag=1;
        int len=max(strlen(name[i]),strlen(names));
        for(int j=0;j<len;j++)
            if (name[i][j]!=names[j]) {flag=0;break;}
        if (flag) return i;
    }
}

int main()
{
    scanf("%d%d",&n,&m);

    insert(s,1,2,0);
    insert(2*n,t,inf,0);
    for(int i=1;i<=n;i++)
    {
        if (i==1||i==n) insert(2*i-1,2*i,2,1);
        else insert(2*i-1,2*i,1,1);
        scanf("%s",name[i]);
    }
    for(int i=1;i<=m;i++)
    {
        int a,b;
        scanf("%s",names);
        a=find();
        scanf("%s",names);
        b=find();
        if (a>b) swap(a,b);
        insert(2*a,2*b-1,inf,0);
    }

    int ans=maxcost()-2;
    if (maxf<2) printf("No Solution!");
    else
    {
        printf("%d\n",ans);
        output();
    }

    return 0;
}

软件补丁问题

更新时间: 2018.1.23
测试地址:软件补丁问题
做法:本题需要用到费用流。
不难想到,将错误的2n个状态全部存储成点,然后把“使用补丁”这一过程看作状态的转移,用位运算把图建出来,最后从(111...1)(000...0)的最短路径就是答案。没错,一次最短路就能解决的事情为什么要放在网络流24题里呢……虽然这样,但为了达到练习(方便Ctrl+C和Ctrl+V)的效果,我的代码还是用费用流的模板写的。
然而,要注意的是,如果我们在做之前就把图建好,结果是无论如何都会MLE或者RE,这时我们就要动态加边,就是等到用到时才把边建出来,我也不知道为什么加了这个之后就能过了,大概是玄学吧……
以下是本人代码:

#include <bits/stdc++.h>
#define s 0
#define t (1<<n)+1
#define ll long long
#define inf 1000000000
using namespace std;
int n,m,first[2000010]={0},tot=1,laste[2000010],last[2000010];
int b1[110]={0},b2[110]={0},f1[110]={0},f2[110]={0};
ll dis[2000010],tim[110];
bool vis[2000010]={0},visit[2000010]={0};
struct edge {int v,next;ll f,c;} e[2000010];
queue <int> Q;

void insert(int a,int b,ll f,ll c)
{
    e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,e[tot].c=c,first[a]=tot;
    e[++tot].v=a,e[tot].next=first[b],e[tot].f=0,e[tot].c=-c,first[b]=tot;
}

bool spfa()
{
    Q.push(s);
    dis[s]=0;vis[s]=1;
    for(int i=1;i<=t;i++)
        dis[i]=inf;
    while(!Q.empty())
    {
        int v=Q.front();Q.pop();
        if (v&&!visit[v])
        {
            v--;
            for(int i=1;i<=m;i++)
                if ((v&b1[i])==b1[i]&&(v&b2[i])==0)
                    insert(v+1,((v-(v&f1[i]))|f2[i])+1,1,tim[i]);
            v++;
            visit[v]=1;
        }
        for(int i=first[v];i;i=e[i].next)
            if (e[i].f&&dis[v]+e[i].c<dis[e[i].v])
            {
                dis[e[i].v]=dis[v]+e[i].c;
                laste[e[i].v]=i;
                last[e[i].v]=v;
                if (!vis[e[i].v]) Q.push(e[i].v),vis[e[i].v]=1;
            }
        vis[v]=0;
    }
    return dis[t]!=inf;
}

ll mincost()
{
    ll ans=0;
    while(spfa())
    {
        int x=t;
        ll minf=inf;
        while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x];
        x=t;
        while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x];
        ans+=minf*dis[t];
    }
    return ans;
}

int main()
{
    scanf("%d%d",&n,&m);
    insert(s,1<<n,1,0);
    insert(1,t,1,0);
    for(int i=1;i<=m;i++)
    {
        char b[20],f[20];
        scanf("%lld%s%s",&tim[i],b,f);
        for(int j=0;j<n;j++)
        {
            if (b[j]=='+') b1[i]+=(1<<j);
            if (b[j]=='-') b2[i]+=(1<<j);
            if (f[j]=='-') f1[i]+=(1<<j);
            if (f[j]=='+') f2[i]+=(1<<j);
        }
    }

    printf("%lld",mincost());

    return 0;
}

数字梯形问题

更新时间: 2018.1.25
测试地址:数字梯形问题
做法:本题需要用到费用流。
对于这道题,因为每次只能往左下和右下两个数字走,所以边相交的情况实际上只有重边的情况,所以直接考虑网络流建模。
对于第一问,相当于限制了点容量和往下走的边容量,因此惯例把每个点拆成两个点即可,剩下的建模就直接按照转移图建就行。
对于第二问,相当于只限制了往下走的边容量,这时要注意,从最下面一行到汇点的边不属于限制边,因为有可能出现路径在最后一行相交的情况。这时我们只需把点容量限制取消即可。
对于第三问,相当于没有限制,取消所有限制即可。
按照上述方式建完图后,在点容量的边上加一个费用,即这个点上数的值,然后跑最大费用最大流即可。
以下是本人代码:

#include<bits/stdc++.h>
#define ll long long
#define s 0
#define t 509
#define inf 1000000000
using namespace std;
int m,n,p=0,first[1010]={0},tot=1;
int last[1010],laste[1010];
ll dis[1010];
struct edge {int v,next,type;ll f,c;} e[200010];
queue <int> Q;
bool vis[1010]={0};

void insert(int a,int b,ll f,ll c,int type)
{
    e[++tot].v=b,e[tot].f=f,e[tot].c=c,e[tot].type=type,e[tot].next=first[a],first[a]=tot;
    e[++tot].v=a,e[tot].f=0,e[tot].c=-c,e[tot].type=type,e[tot].next=first[b],first[b]=tot;
}

bool spfa()
{
    dis[s]=0;
    for(int i=1;i<=2*p;i++) dis[i]=-inf;
    dis[t]=-inf;
    vis[s]=1;
    Q.push(s);
    while(!Q.empty())
    {
        int v=Q.front();Q.pop();
        for(int i=first[v];i;i=e[i].next)
            if (e[i].f&&dis[e[i].v]<dis[v]+e[i].c)
            {
                laste[e[i].v]=i;
                last[e[i].v]=v;
                dis[e[i].v]=dis[v]+e[i].c;
                if (!vis[e[i].v]) Q.push(e[i].v),vis[e[i].v]=1;
            }
        vis[v]=0;
    }
    return dis[t]>0;
}

ll maxcost()
{
    ll ans=0,minf;
    while(spfa())
    {
        minf=inf;
        int x=t;
        while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x];
        x=t;
        while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x];
        ans+=minf*dis[t];
    }
    return ans;
}

int main()
{
    scanf("%d%d",&m,&n);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m+i-1;j++)
        {
            ll a;
            scanf("%lld",&a);
            p++;
            if (i==1) insert(s,2*p-1,1,0,0);
            if (i==n) insert(2*p,t,inf,0,1);
            insert(2*p-1,2*p,1,a,1);
            if (i>1&&j>1) insert(2*(p-m-i+1),2*p-1,1,0,2);
            if (i>1&&j<m+i-1) insert(2*(p-m-i+2),2*p-1,1,0,2);
        }

    printf("%lld\n",maxcost());
    for(int i=2;i<=tot;i+=2)
    {
        if (e[i].type==1) e[i].f=inf;
        else e[i].f=1;
        e[i^1].f=0;
    }
    printf("%lld\n",maxcost());
    for(int i=2;i<=tot;i+=2)
    {
        if (e[i].type>0) e[i].f=inf;
        else e[i].f=1;
        e[i^1].f=0;
    }
    printf("%lld",maxcost());

    return 0;
}

运输问题

更新时间: 2018.1.25
测试地址:运输问题
做法:本题需要用到费用流。
做了那么多题的你看到这样的题,应该很快就能想出建模方法了:
对于所有仓库i,连接(S,i,ai,0)
对于所有商店j,连接(j,T,bj,0)
对于所有运输方法i->j,连接(i,j,inf,cij)
对上面的网络,分别跑一次最小费用最大流和最大费用最大流即可。
以下是本人代码:

#include<bits/stdc++.h>
#define ll long long
#define s 0
#define t m+n+1
using namespace std;
int m,n,first[210]={0},tot=1;
int last[210],laste[210];
ll dis[210],inf;
struct edge {int v,next;ll f,c;} e[200010];
queue <int> Q;
bool vis[210]={0};

void insert(int a,int b,ll f,ll c)
{
    e[++tot].v=b,e[tot].f=f,e[tot].c=c,e[tot].next=first[a],first[a]=tot;
    e[++tot].v=a,e[tot].f=0,e[tot].c=-c,e[tot].next=first[b],first[b]=tot;
}

bool spfa(bool type)
{
    dis[s]=0;
    for(int i=1;i<=t;i++) dis[i]=type?inf:-inf;
    vis[s]=1;
    Q.push(s);
    while(!Q.empty())
    {
        int v=Q.front();Q.pop();
        for(int i=first[v];i;i=e[i].next)
            if (e[i].f&&((!type&&dis[e[i].v]<dis[v]+e[i].c)||(type&&dis[e[i].v]>dis[v]+e[i].c)))
            {
                laste[e[i].v]=i;
                last[e[i].v]=v;
                dis[e[i].v]=dis[v]+e[i].c;
                if (!vis[e[i].v]) Q.push(e[i].v),vis[e[i].v]=1;
            }
        vis[v]=0;
    }
    return dis[t]!=(type?inf:-inf);
}

ll mincost(bool type)
{
    ll ans=0,minf;
    while(spfa(type))
    {
        minf=inf;
        int x=t;
        while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x];
        x=t;
        while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x];
        ans+=minf*dis[t];
    }
    return ans;
}

int main()
{
    inf=1000000000;
    inf*=inf;

    scanf("%d%d",&m,&n);
    for(int i=1;i<=m;i++)
    {
        ll a;
        scanf("%lld",&a);
        insert(s,i,a,0);
    }
    for(int i=1;i<=n;i++)
    {
        ll a;
        scanf("%lld",&a);
        insert(m+i,t,a,0);
    }
    for(int i=1;i<=m;i++)
        for(int j=1;j<=n;j++)
        {
            ll a;
            scanf("%lld",&a);
            insert(i,m+j,inf,a);
        }

    printf("%lld\n",mincost(1));
    for(int i=2;i<=tot;i+=2)
        e[i].f=e[i].f+e[i^1].f,e[i^1].f=0;
    printf("%lld",mincost(0));

    return 0;
}

分配问题

更新时间: 2018.1.25
测试地址:分配问题
做法:本题需要用到费用流。
本题是经典的二分图最佳匹配模型,可以用KM算法做,当然也可以用费用流来解。建图的方法和上面那题基本一样,只不过从源点流出和进入汇点的边容量都是1,建完图后分别跑一次最小费用最大流和最大费用最大流即可。
以下是本人代码:

#include<bits/stdc++.h>
#define ll long long
#define s 0
#define t 2*n+1
using namespace std;
int n,first[210]={0},tot=1;
int last[210],laste[210];
ll dis[210],inf;
struct edge {int v,next;ll f,c;} e[200010];
queue <int> Q;
bool vis[210]={0};

void insert(int a,int b,ll f,ll c)
{
    e[++tot].v=b,e[tot].f=f,e[tot].c=c,e[tot].next=first[a],first[a]=tot;
    e[++tot].v=a,e[tot].f=0,e[tot].c=-c,e[tot].next=first[b],first[b]=tot;
}

bool spfa(bool type)
{
    dis[s]=0;
    for(int i=1;i<=t;i++) dis[i]=type?inf:-inf;
    vis[s]=1;
    Q.push(s);
    while(!Q.empty())
    {
        int v=Q.front();Q.pop();
        for(int i=first[v];i;i=e[i].next)
            if (e[i].f&&((!type&&dis[e[i].v]<dis[v]+e[i].c)||(type&&dis[e[i].v]>dis[v]+e[i].c)))
            {
                laste[e[i].v]=i;
                last[e[i].v]=v;
                dis[e[i].v]=dis[v]+e[i].c;
                if (!vis[e[i].v]) Q.push(e[i].v),vis[e[i].v]=1;
            }
        vis[v]=0;
    }
    return dis[t]!=(type?inf:-inf);
}

ll mincost(bool type)
{
    ll ans=0,minf;
    while(spfa(type))
    {
        minf=inf;
        int x=t;
        while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x];
        x=t;
        while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x];
        ans+=minf*dis[t];
    }
    return ans;
}

int main()
{
    inf=1000000000;
    inf*=inf;

    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        insert(s,i,1,0);
        insert(n+i,t,1,0);
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            ll a;
            scanf("%lld",&a);
            insert(i,n+j,inf,a);
        }

    printf("%lld\n",mincost(1));
    for(int i=2;i<=tot;i+=2)
        e[i].f=e[i].f+e[i^1].f,e[i^1].f=0;
    printf("%lld",mincost(0));

    return 0;
}

负载平衡问题

更新时间: 2018.1.25
测试地址:负载平衡问题
做法:本题需要用到费用流。
虽然这题貌似可以用贪心或者中位数啥啥的做法水过,但是这题的数据范围明显适用网络流,而且我们很快就能想出建模的方法。
对于每个仓库,建立一个供给节点和一个需求节点,对于这道题,每个仓库的供给量就是一开始的货物数,需求量就是总货物数/仓库数,而一份货物从一个仓库转移到另一个仓库需要一些代价。看到这里,其实建模思路已经非常明显了,按照运输问题那样的建模方式建,然后跑最小费用最大流即可。
以下是本人代码:

#include<bits/stdc++.h>
#define ll long long
#define s 0
#define t 2*n+1
#define inf 1000000000
using namespace std;
int n,p=0,first[210]={0},tot=1;
int last[210],laste[210];
ll dis[210],sum=0;
struct edge {int v,next;ll f,c;} e[200010];
queue <int> Q;
bool vis[210]={0};

void insert(int a,int b,ll f,ll c)
{
    e[++tot].v=b,e[tot].f=f,e[tot].c=c,e[tot].next=first[a],first[a]=tot;
    e[++tot].v=a,e[tot].f=0,e[tot].c=-c,e[tot].next=first[b],first[b]=tot;
}

bool spfa()
{
    dis[s]=0;
    for(int i=1;i<=t;i++) dis[i]=inf;
    vis[s]=1;
    Q.push(s);
    while(!Q.empty())
    {
        int v=Q.front();Q.pop();
        for(int i=first[v];i;i=e[i].next)
            if (e[i].f&&dis[e[i].v]>dis[v]+e[i].c)
            {
                laste[e[i].v]=i;
                last[e[i].v]=v;
                dis[e[i].v]=dis[v]+e[i].c;
                if (!vis[e[i].v]) Q.push(e[i].v),vis[e[i].v]=1;
            }
        vis[v]=0;
    }
    return dis[t]!=inf;
}

ll mincost()
{
    ll ans=0,minf;
    while(spfa())
    {
        minf=inf;
        int x=t;
        while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x];
        x=t;
        while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x];
        ans+=minf*dis[t];
    }
    return ans;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        ll a;
        scanf("%lld",&a);
        sum+=a;
        insert(s,i,a,0);
    }
    for(int i=1;i<=n;i++) insert(n+i,t,sum/n,0);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            insert(i,n+j,inf,min(abs(i-j),n-abs(i-j)));

    printf("%lld",mincost());

    return 0;
}

深海机器人问题

更新时间: 2018.1.26
测试地址:深海机器人问题
做法:本题需要用到费用流。
这题唯一比较难的点是如何处理每条边只能取一次,但其实也不难,只需要先连(u,v,1,x),然后再连一个(u,v,inf,0)即可,其他建图应该比较容易了,建完之后跑最大费用最大流即可。
以下是本人代码:

#include<bits/stdc++.h>
#define s 0
#define t (p+1)*(q+1)+1
#define ll long long
using namespace std;
int p,q,a,b,first[510]={0},tot=1,last[510],laste[510];
ll dis[510],inf;
struct edge {int v,next;ll f,c;} e[200010];
bool vis[510]={0};
queue <int> Q;

void insert(int a,int b,ll f,ll c)
{
    e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,e[tot].c=c,first[a]=tot;
    e[++tot].v=a,e[tot].next=first[b],e[tot].f=0,e[tot].c=-c,first[b]=tot;
}

bool spfa()
{
    dis[s]=0,vis[s]=1;
    for(int i=1;i<=t;i++)
        dis[i]=-inf;
    Q.push(s);
    while(!Q.empty())
    {
        int v=Q.front();Q.pop();
        for(int i=first[v];i;i=e[i].next)
            if (e[i].f&&dis[e[i].v]<dis[v]+e[i].c)
            {
                dis[e[i].v]=dis[v]+e[i].c;
                last[e[i].v]=v;
                laste[e[i].v]=i;
                if (!vis[e[i].v]) vis[e[i].v]=1,Q.push(e[i].v);
            }
        vis[v]=0;
    }
    return dis[t]!=-inf;
}

ll maxcost()
{
    ll ans=0;
    while(spfa())
    {
        int x=t;
        ll minf=inf;
        while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x];
        x=t;
        while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x];
        ans+=dis[t]*minf;
    }
    return ans;
}

int main()
{
    inf=1000000000;
    inf*=inf;

    scanf("%d%d%d%d",&a,&b,&p,&q);
    for(int i=0;i<p+1;i++)
        for(int j=0;j<q;j++)
        {
            ll x;
            scanf("%lld",&x);
            insert(j*(p+1)+i+1,(j+1)*(p+1)+i+1,1,x);
            insert(j*(p+1)+i+1,(j+1)*(p+1)+i+1,inf,0);
        }
    for(int j=0;j<q+1;j++)
        for(int i=0;i<p;i++)
        {
            ll x;
            scanf("%lld",&x);
            insert(j*(p+1)+i+1,j*(p+1)+i+2,1,x);
            insert(j*(p+1)+i+1,j*(p+1)+i+2,inf,0);
        }
    for(int i=1;i<=a;i++)
    {
        ll k;int x,y;
        scanf("%lld%d%d",&k,&x,&y);
        swap(x,y);
        insert(s,x*(p+1)+y+1,k,0);
    }
    for(int i=1;i<=b;i++)
    {
        ll k;int x,y;
        scanf("%lld%d%d",&k,&x,&y);
        swap(x,y);
        insert(x*(p+1)+y+1,t,k,0);
    }

    printf("%lld",maxcost());

    return 0;
}

最长k可重区间集问题

更新时间: 2018.1.26
测试地址:最长k可重区间集问题
做法:本题需要用到费用流。
尝试建一个有向图:将一个区间看做一个点,点权为该区间的长度,若两个区间i,j不相交,且ij之前,则连接i->j。不难看出,问题可以转化为在这个有向图中选取k条不相交的路径,使得路径上的点权总和最大。因为每个点都有可能是起点或者终点,所以从S向所有点、从所有点向T连边,而又因为题目只限制了总流量,所以应该将S拆成两个点,然后连接(S1,S2,k)。题目要求路径不相交,那么每个点肯定只能经过一次,因此将每个点拆成两个点,然后连接(xi,yi,1,valuei)。其他的边就按照原有向图的边连即可,容量为inf,费用为0,建完图后跑一遍最大费用最大流即可。
(据说还有另一种建模方式,有待学习)
以下是本人代码:

#include<bits/stdc++.h>
#define s 0
#define ss 2*n+2
#define t 2*n+1
#define ll long long
using namespace std;
int n,k,first[1010]={0},tot=1,last[1010],laste[1010];
ll l[1010],r[1010],dis[1010],inf;
struct edge {int v,next;ll f,c;} e[500010];
bool vis[1010]={0};
queue <int> Q;

void insert(int a,int b,ll f,ll c)
{
    e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,e[tot].c=c,first[a]=tot;
    e[++tot].v=a,e[tot].next=first[b],e[tot].f=0,e[tot].c=-c,first[b]=tot;
}

bool spfa()
{
    dis[s]=0,vis[s]=1;
    for(int i=1;i<=ss;i++)
        dis[i]=-inf;
    Q.push(s);
    while(!Q.empty())
    {
        int v=Q.front();Q.pop();
        for(int i=first[v];i;i=e[i].next)
            if (e[i].f&&dis[e[i].v]<dis[v]+e[i].c)
            {
                dis[e[i].v]=dis[v]+e[i].c;
                last[e[i].v]=v;
                laste[e[i].v]=i;
                if (!vis[e[i].v]) vis[e[i].v]=1,Q.push(e[i].v);
            }
        vis[v]=0;
    }
    return dis[t]!=-inf;
}

ll maxcost()
{
    ll ans=0;
    while(spfa())
    {
        int x=t;
        ll minf=inf;
        while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x];
        x=t;
        while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x];
        ans+=dis[t]*minf;
    }
    return ans;
}

int main()
{
    inf=1000000000;
    inf*=inf;

    scanf("%d%d",&n,&k);
    insert(s,ss,k,0);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld%lld",&l[i],&r[i]);
        insert(ss,2*i-1,inf,0);
        insert(2*i,t,inf,0);
        insert(2*i-1,2*i,1,r[i]-l[i]);
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if (l[j]>=r[i]) insert(2*i,2*j-1,inf,0);

    printf("%lld",maxcost());

    return 0;
}

最长k可重线段集问题

更新时间: 2018.1.27
测试地址:最长k可重线段集问题
做法:本题需要用到费用流。
本题的做法和上面的最长k可重区间集差不多,区别有:
1.线段长度的计算不同了(…废话),注意在洛谷上必须要开long long才不会爆。
2.不同于区间,开区间的左右端点不可能是一个数,但是开线段的两个端点的横坐标可能相同,也就是垂直于x轴,解决方法是:将所有横坐标乘以2,如果一条线段两端点的横坐标相同,那么右端点++,否则左端点++,这样就可以将左右端点隔开了。
其他建模方法和上一题相同,详见代码。
以下是本人代码:

#include<bits/stdc++.h>
#define s 0
#define ss 2*n+2
#define t 2*n+1
#define ll long long
using namespace std;
int n,k,first[1010]={0},tot=1,last[1010],laste[1010];
ll xz[1010],yz[1010],xo[1010],yo[1010],dis[1010],inf;
struct edge {int v,next;ll f,c;} e[500010];
bool vis[1010]={0};
queue <int> Q;

void insert(int a,int b,ll f,ll c)
{
    e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,e[tot].c=c,first[a]=tot;
    e[++tot].v=a,e[tot].next=first[b],e[tot].f=0,e[tot].c=-c,first[b]=tot;
}

bool spfa()
{
    dis[s]=0,vis[s]=1;
    for(int i=1;i<=ss;i++)
        dis[i]=-inf;
    Q.push(s);
    while(!Q.empty())
    {
        int v=Q.front();Q.pop();
        for(int i=first[v];i;i=e[i].next)
            if (e[i].f&&dis[e[i].v]<dis[v]+e[i].c)
            {
                dis[e[i].v]=dis[v]+e[i].c;
                last[e[i].v]=v;
                laste[e[i].v]=i;
                if (!vis[e[i].v]) vis[e[i].v]=1,Q.push(e[i].v);
            }
        vis[v]=0;
    }
    return dis[t]!=-inf;
}

ll maxcost()
{
    ll ans=0;
    while(spfa())
    {
        int x=t;
        ll minf=inf;
        while(x!=s) minf=min(minf,e[laste[x]].f),x=last[x];
        x=t;
        while(x!=s) e[laste[x]].f-=minf,e[laste[x]^1].f+=minf,x=last[x];
        ans+=dis[t]*minf;
    }
    return ans;
}

int main()
{
    inf=1000000000;
    inf*=inf;

    scanf("%d%d",&n,&k);
    insert(s,ss,k,0);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld%lld%lld%lld",&xz[i],&yz[i],&xo[i],&yo[i]);
        if (xo[i]<xz[i]) swap(xz[i],xo[i]),swap(yz[i],yo[i]);
        insert(ss,2*i-1,inf,0);
        insert(2*i,t,inf,0);
        insert(2*i-1,2*i,1,(ll)sqrt((xo[i]-xz[i])*(xo[i]-xz[i])+(yo[i]-yz[i])*(yo[i]-yz[i])));
        xz[i]<<=1,xo[i]<<=1;
        if (xz[i]==xo[i]) xo[i]++;
        else xz[i]++;
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if (xz[j]>=xo[i]) insert(2*i,2*j-1,inf,0);

    printf("%lld",maxcost());

    return 0;
}

不可做问题

机器人路径规划问题

更新时间: 2018.1.21
测试地址:机器人路径规划问题
做法:本题是这24题中唯一一道不能用网络流算法解决的问题,需要用一堆诡异的随机化算法解决,但据说有大牛弄出来了O(n6)的DP做法,这里只附个链接供大家膜拜:
大佬 Orz <-我

posted @ 2018-01-19 11:25  Maxwei_wzj  阅读(197)  评论(0编辑  收藏  举报