zoukankan      html  css  js  c++  java
  • 网络流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
    ",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
    ",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("
    ");
            }
        for(int i=first[0];i;i=e[i].next)
            if (!vis[(e[i].v+1)/2]) printf("%d
    ",(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("
    ");
            }
        for(int i=first[0];i;i=e[i].next)
            if (!vis[(e[i].v+1)/2]&&e[i].v<=m) printf("%d
    ",(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
    ",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("
    ");
        }
    }
    
    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
    ");
            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
    ",mx);
        printf("%d
    ",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("
    ");
        }
    }
    
    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=='
    '||c=='
    ');
        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("
    ");
        for(int i=1;i<=n;i++)
            if (lvl[i+m]!=-1) printf("%d ",i);
        printf("
    %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
    ",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
    ",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
    ",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
    ",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
    ",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
    ",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
    ",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
    ",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
    ",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 <-我

  • 相关阅读:
    ListView的item中EditText编辑(或者其他控件)修改本行数据
    C#:MVC引用Log4Net生成错误日志
    Web Developer教程
    EditPlus高级使用技巧(附视频、课件、代码下载)
    jQuery入门篇
    网摘系统架构
    BugFree 2.0使用帮助
    使用 WebDeployment Project 视频
    BugFreeHelper 2.2 For BugFree2.0(RTM)
    FireFox3推荐安装附加组件Top10(附官方主页和下载地址)
  • 原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793539.html
Copyright © 2011-2022 走看看