【上下界网络流】sgu194 zoj3229 sgu176 zoj1994 zoj3496

终于意识到了一个好的网络流模板是有多么重要。%>_<%
而且上下界的代码细节特别多,再加上蒟蒻木有写一段就检查的好习惯,直接导致调了很久……

sgu194
这是一道无源点汇点的求上下界网络流的可行流。
首先把它转化为一个有源点汇点的网络流。令 du[u] 表示出边的下界之和减去入边的下界之和。把所有边的流量改为 updown (上界-下界)。
建立一个超级源点 s 和超级汇点 t 。若点 u du[u]>0 ,从 s 连一条边到 u 流量为 du[u] ;否则从 u 连一条边到 t 流量为 du[u] 。之后做一次最大流。
s 的邻接边满流,说明该图的下界均能满足,则存在可行流。

#include <iostream>
#include <cstdio>
#include <queue>
#define MAXN 205
#define MAXM 40005
#define inf 2147483647
using namespace std;

int n, m, low[MAXM], flow[MAXM];
int a, b, e, du[MAXN];

struct node
{
    int v, cap, back, next;
}edge[MAXM];
int adj[MAXN], pos;

inline void add(int a,int b,int c)
{
    ++pos;
    edge[pos].v=b, edge[pos].cap=c, edge[pos].back=pos+1, edge[pos].next=adj[a];
    adj[a]=pos;

    ++pos;
    edge[pos].v=a, edge[pos].cap=0, edge[pos].back=pos-1, edge[pos].next=adj[b];
    adj[b]=pos;
}

bool in[MAXN];
int d[MAXN], vd[MAXN];
void spfa()
{
    for(int i=b;i<=e;++i)d[i]=inf;
    queue<int>q;
    q.push(e), d[e]=0;
    int u, v;
    while(!q.empty())
    {
        u=q.front(), in[u]=0;
        q.pop();
        for(int p=adj[u];p;p=edge[p].next)
            if(edge[edge[p].back].cap>0&&d[(v=edge[p].v)]>d[u]+1)
            {
                d[v]=d[u]+1;
                if(!in[v])
                    q.push(v), in[v]=1;
            }
    }
}

int aug(int u,int augco)
{
    if(u==e)return augco;
    int augc=augco, delta, v, mind=e+e;
    for(int p=adj[u];p&&augc;p=edge[p].next)
    {
        if(edge[p].cap>0)
        {
            v=edge[p].v;
            if(edge[p].cap>0&&d[u]==d[v]+1)
            {
                delta=aug(v,min(augc,edge[p].cap));
                flow[p]+=delta, flow[edge[p].back]-=delta;
                edge[p].cap-=delta, edge[edge[p].back].cap+=delta, augc-=delta;
                if(d[b]>=e)break;
            }
            mind=min(mind,d[v]);
        }
    }
    if(augco==augc)
    {
        --vd[d[u]];
        if(!vd[d[u]])d[b]=e+1;
        d[u]=mind+1;
        ++vd[d[u]];
    }
    return augco-augc;
}

void solve()
{
    spfa();
    for(int i=b;i<=e;++i)++vd[d[i]];
    while(d[b]<=e)
        aug(b,inf);
}

void work()
{
    for(int p=adj[b];p;p=edge[p].next)
        if(edge[p].cap>0)
        {
            puts("NO");
            return;
        }
    puts("YES");
    for(int i=1;i<=m;++i)
        printf("%d\n",flow[i*2-1]+low[i]);
}

int main()
{
    int d;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;++i)
    {
        scanf("%d%d%d%d",&a,&b,&low[i],&d);
        du[a]-=low[i], du[b]+=low[i];
        add(a,b,d-low[i]);
    }
    b=0, e=n+1;
    for(int i=1;i<=n;++i)
        if(du[i]>0)add(b,i,du[i]);
        else add(i,e,-du[i]);
    solve();
    work();
    return 0;
}

zoj3229
题目大意:有一个屌丝要给 m 个女神照相,分为 n 天。每个女神最多照 Gi 张照。第 i 天来了 C 个女神,第 T 个女神照的照片范围为 [L,R] ,而屌丝只能照 Di 张照片。求最多能照多少张照片。

这个建图应该比较容易吧~
一个源点连接 1n 号点(表示第几天),边的上界为 Di i 号点向 C 个女神连边,每一条边的上界为 R ,下界为 L m 个女神向汇点连边,上界为 Gi

这就是一个有源点汇点的求最大流的问题。
首先要保证它有可行流。从汇点连一条边到源点,流量为 + 。这就转化为一个无源点汇点。
求出可行流之后,删除超级源点和超级汇点,再做一次最大流即可。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define MAXN 405
#define MAXM 1005
#define inf 0x3f3f3f3f
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;

int n, m, adj[MAXN+MAXM], pos, low[MAXN][MAXM], eid[MAXN][MAXM];
int du[MAXN+MAXM], d[MAXN+MAXM], vd[MAXN+MAXM];
int b, e, a, c, l, k;

struct node
{
    int v, cap, back, next;
}edge[MAXN*MAXM];
int flow[MAXN*MAXM];

inline void add(int a,int b,int c)
{
    ++pos;
    edge[pos].v=b, edge[pos].cap=c;
    edge[pos].back=pos+1, edge[pos].next=adj[a];
    adj[a]=pos;

    ++pos;
    edge[pos].v=a, edge[pos].cap=0;
    edge[pos].back=pos-1, edge[pos].next=adj[b];
    adj[b]=pos;
}

queue<int>q;
bool in[MAXN+MAXM];
void spfa()
{
    memset(d,inf,sizeof d);
    q.push(e), d[e]=0;
    int u, v;
    while(!q.empty())
    {
        u=q.front(), in[u]=0;
        q.pop();
        for(int p=adj[u];p;p=edge[p].next)
        {
            if(edge[edge[p].back].cap>=0&&d[(v=edge[p].v)]>d[u]+1)
            {
                d[v]=d[u]+1;
                if(!in[v])q.push(v), in[v]=1;
            }
        }
    }
}

int aug(int u,int augco)
{
    if(u==e)return augco;
    int augc=augco, delta, mind=e+e, v;
    for(int p=adj[u];p&&augc;p=edge[p].next)
        if(edge[p].cap>0)
        {
            v=edge[p].v;
            if(d[v]+1==d[u])
            {
                delta=aug(v,min(augc,edge[p].cap));
                flow[p]+=delta, flow[edge[p].back]-=delta;
                edge[p].cap-=delta, edge[edge[p].back].cap+=delta, augc-=delta;
                if(d[b]>e)break;
            }
            mind=min(mind,d[v]);
        }
    if(augco==augc)
    {
        --vd[d[u]];
        if(!vd[d[u]])d[b]=e+1;
        d[u]=mind+1;
        ++vd[d[u]];
    }
    return augco-augc;
}

int solve(int n,int m)
{
    b=n, e=m;
    spfa();
    memset(vd,0,sizeof vd);
    for(int i=0;i<=m;++i)++vd[d[i]];
    int ans=0;
    while(d[b]<=e)
        ans+=aug(b,inf);
    return ans;
}

inline void init()
{
    memset(du,0,sizeof du);
    memset(flow,0,sizeof flow);
    memset(eid,0,sizeof eid), memset(low,0,sizeof low);
    memset(adj,0,sizeof adj), pos=0;
}

int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        init();
        b=n+m, e=n+m+1;
        for(int i=0;i<m;++i)
        {
            scanf("%d",&a);
            du[e]+=a, du[i+n]-=a;
            add(i+n,e,inf-a);
        }
        for(int i=0;i<n;++i)
        {
            scanf("%d%d",&k,&a);
            add(b,i,a);
            for(int j=0;j<k;++j)
            {
                scanf("%d%d%d",&a,&l,&c);
                du[i]-=l, du[a+n]+=l, low[i][a]=l;
                eid[i][a]=pos+1;
                add(i,a+n,c-l);
            }
        }
        add(e,b,inf);
        b=e+1;
        e=e+2;
        int sum=0;
        for(int i=0;i<=e-2;++i)
            if(du[i]>0)
                sum+=du[i], add(b,i,du[i]);
            else
                add(i,e,-du[i]);
        if(sum!=solve(b,e))
            printf("-1\n");
        else
        {
            adj[b]=adj[e]=0;
            e-=2, b=e-1;
            adj[e]=edge[adj[e]].next;
            int tmp=solve(b,e);
            printf("%d\n",tmp);
            for(int i=0;i<n;++i)
                for(int j=0;j<m;++j)
                    if(eid[i][j])
                        printf("%d\n",flow[eid[i][j]]+low[i][j]);
        }
        puts("");
    }
    return 0;
}

sgu176
题目大意:在一个网络中有M条管道。若 k=1 ,该管道必须满流。若 k=0 ,则该管道的上界为 Z 。源点为 1 ,汇点为 n 。求此网络流中的最小流。

求最小流和最大流的方法差不多。只是先不连一条边(从汇点到源点的一条边,流量为 + )。做一次最大流,再连上这条边。再做一次最大流。此时, t s 的流量即是最小流。

第一次写,对边集数组的大小拿不准,RE了几次= =

#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#define inf 0x3f3f3f3f
#define MAXN 10005
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;

int n, m, adj[MAXN], pos, a, b, c, e, k;
int d[MAXN], vd[MAXN];
int du[MAXN];

struct node
{
    int v, cap, back, next, flow;
}edge[MAXN<<1];
int flow[MAXN], eid[MAXN];

inline void add(int a,int b,int c)
{
    ++pos;
    edge[pos].v=b, edge[pos].cap=c, edge[pos].next=adj[a], edge[pos].back=pos+1;
    adj[a]=pos;

    ++pos;
    edge[pos].v=a, edge[pos].cap=0, edge[pos].next=adj[b], edge[pos].back=pos-1;
    adj[b]=pos;
}

queue<int>q;
bool in[MAXN];
void spfa()
{
    for(int i=1;i<=e;++i)d[i]=inf;
    q.push(e), d[e]=0;
    int u, v;
    while(!q.empty())
    {
        u=q.front(), in[u]=0;
        q.pop();
        for(int p=adj[u];p;p=edge[p].next)
            if(edge[edge[p].back].cap>0)
                if(d[(v=edge[p].v)]>d[u]+1)
                {
                    d[v]=d[u]+1;
                    if(!in[v])q.push(v), in[v]=1;
                }
    }
}

int aug(int u,int augco)
{
    if(u==e)return augco;
    int delta, augc=augco, mind=e+e, v;
    for(int p=adj[u];p&&augc;p=edge[p].next)
        if(edge[p].cap>0)
        {
            if(d[(v=edge[p].v)]+1==d[u])
            {
                delta=aug(v,min(edge[p].cap,augc));
                augc-=delta, edge[p].cap-=delta, edge[edge[p].back].cap+=delta;
                edge[p].flow+=delta, edge[edge[p].back].flow-=delta;
                if(d[b]>e)break;
            }
            mind=min(mind,d[v]);
        }
    if(augco==augc)
    {
        --vd[d[u]];
        if(!vd[d[u]])d[b]=e+1;
        d[u]=mind+1;
        ++vd[d[u]];
    }
    return augco-augc;
}

int solve()
{
    int ans=0;
    spfa();
    memset(vd,0,sizeof vd);
    for(int i=1;i<=e;++i)
        if(d[i]!=inf)++vd[d[i]];
    while(d[b]<=e)
        ans+=aug(b,inf);
    return ans;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;++i)
    {
        scanf("%d%d%d%d",&a,&b,&c,&k);
        if(k)du[a]-=c, du[b]+=c, flow[i]=c;
        else eid[i]=pos+1, add(a,b,c);
    }
    b=n+1, e=n+2;
    for(int i=1;i<=n;++i)
        if(du[i]>0)add(b,i,du[i]);
        else add(i,e,-du[i]);
    solve();
    add(n,1,inf);
    solve();
    bool flag=0;
    for(int p=adj[b];p;p=edge[p].next)
        if(edge[p].cap>0)
        {
            flag=1;
            break;
        }
    if(flag)puts("Impossible");
    else
    {
        for(int p=adj[n];p;p=edge[p].next)
            if(edge[p].v==1)
            {
                printf("%d\n",edge[p].flow);
                break;
            }
        for(int i=1;i<m;++i)
            printf("%d ",edge[eid[i]].flow+flow[i]);
        printf("%d\n",edge[eid[m]].flow+flow[m]);
    }
    return 0;
}

zoj1994
题目大意:一个矩阵 nm ,已知每一列每一行的和。之后有 k 个限制条件。若有一个坐标为 0 ,则表示某一行或某一列的所有数都满足这个条件。求这个矩阵。

一开始我想的是建立 nm 个点,然后发现不好建图。
之后就想到的用第 i 行,第 j 列之间的流量来表示 (i,j) 这个点。
这就比较好建图了~(≧▽≦)/~

然后求一个可行流。

#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#define MAXN 305
#define inf 0x3f3f3f3f
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;

int n, m, up[MAXN][MAXN], down[MAXN][MAXN];
int cap[MAXN][MAXN], flow[MAXN][MAXN], d[MAXN], vd[MAXN], du[MAXN];
int x, y, z, b, e, a, k, sum;
char w[5];

void work(int x,int y,char w)
{
    if(w=='=')up[x][y+n]=down[x][y+n]=z;
    else if(w=='>')down[x][y+n]=max(down[x][y+n],z+1);
    else up[x][y+n]=min(up[x][y+n],z-1);
}

void build()
{
    sum=0;
    memset(down,0,sizeof down), memset(up,0,sizeof up);
    memset(flow,0,sizeof flow), memset(cap,0,sizeof cap);
    memset(du,0,sizeof du);

    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            up[i][j+n]=inf;
    b=n+m+1, e=b+1;
    for(int i=1;i<=n;++i)
    {
        scanf("%d",&a);
        up[b][i]=down[b][i]=a;
    }
    for(int j=1;j<=m;++j)
    {
        scanf("%d",&a);
        up[j+n][e]=down[j+n][e]=a;
    }
    scanf("%d",&k);
    while(k--)
    {
        scanf("%d%d%s%d",&x,&y,w,&z);
        if(!x&&!y)
        {
            for(int i=1;i<=n;++i)
                for(int j=1;j<=m;++j)
                    work(i,j,w[0]);
        }
        else if(!x&&y)
            for(int i=1;i<=n;++i)
                work(i,y,w[0]);
        else if(x&&!y)
            for(int j=1;j<=m;++j)
                work(x,j,w[0]);
        else work(x,y,w[0]);
    }
    for(int i=1;i<=e;++i)
        for(int j=1;j<=e;++j)
        {
            cap[i][j]=up[i][j]-down[i][j];
            du[i]-=down[i][j], du[j]+=down[i][j];
        }
    b=e+1, e=b+1;
    for(int i=1;i<b;++i)
        if(du[i]>0)cap[b][i]=du[i], sum+=du[i];
        else cap[i][e]=-du[i];
}

bool in[MAXN];
void spfa()
{
    memset(d,inf,sizeof d);
    queue<int>q;
    d[e]=0, q.push(e);
    int u;
    while(!q.empty())
    {
        u=q.front(), in[u]=0;
        q.pop();
        for(int i=1;i<=e;++i)
            if(cap[i][u]>0&&d[i]>d[u]+1)
            {
                d[i]=d[u]+1;
                if(!in[i])in[i]=1, q.push(i);
            }
    }
}

int aug(int u,int augco)
{
    if(u==e)return augco;
    int delta, mind=e+e, augc=augco;
    for(int i=1;i<=e&&augc;++i)
        if(cap[u][i]>0)
        {
            mind=min(mind,d[i]);
            if(d[i]+1==d[u])
            {
                delta=aug(i,min(cap[u][i],augc));
                augc-=delta, cap[u][i]-=delta, cap[i][u]+=delta;
                flow[u][i]+=delta, flow[i][u]-=delta;
                if(d[b]>e)break;
            }
        }
    if(augc==augco)
    {
        --vd[d[u]];
        if(!vd[d[u]])d[b]=e+1;
        d[u]=mind+1;
        ++vd[d[u]];
    }
    return augco-augc;
}

int solve()
{
    int ans=0;
    spfa();
    memset(vd,0,sizeof vd);
    for(int i=1;i<=e;++i)
        if(d[i]!=inf)++vd[d[i]];
    while(d[b]<=e)
        ans+=aug(b,inf);
    return ans;
}

int main()
{
    int cas, ans;
    scanf("%d",&cas);
    while(cas--)
    {
        build();
        cap[n+m+2][n+m+1]=inf;
        ans=solve();
        if(ans!=sum)
        {
            puts("IMPOSSIBLE");
            continue;
        }
        cap[n+m+1][n+m+2]=cap[n+m+2][n+m+1]=0;
        solve();
        for(int i=1;i<=n;++i)
        {
            for(int j=1;j<m;++j)
                printf("%d ",flow[i][j+n]+down[i][j+n]);
            printf("%d\n",flow[i][m+n]+down[i][m+n]);
        }
        puts("");
    }
    return 0;
}

zoj3496
题目大意:有一个网络流。有 P 个技能点,在某一条边上使用 i 次,表示该边的费用为 i
有两种情况。第一种,在保证最大流的情况下,A想话费最少,B想收费最多。第二种,在保证最大流的情况下,A想话费最多,B想收费最少。输出这两个答案。

这个技能点的使用肯定是贪心,即全部点在一条边上…
一开始就想到了二分,然而只二分了第二个解…然后就木有搞出来…
然后搜了下题解,两个都要二分…
对于第一个解,实际上是限制上界。对于第二个解,实际上是限制下界。
顿时就变成赤裸裸的大水体…

然而蒟蒻把最大流的总点数弄错了,T了好多次…%>_<%
其实这个网络流的模板效率还可以…

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long int
#define MAXN 605
#define MAXM 20005
#define inf 0x3f3f3f3f
using namespace std;

int n, m, s, t, S, T, adj[MAXN], End;
LL sum;
int a[MAXM], b[MAXM], c[MAXM], maxc, pos, maxflow;
int d[MAXN], du[MAXN], vd[MAXN];

struct node
{
    int v, next, cap;
}edge[MAXM<<1];

inline void add(int a,int b,int c)
{
    edge[pos].v=b, edge[pos].cap=c, edge[pos].next=adj[a];
    adj[a]=pos;
    ++pos;

    edge[pos].v=a, edge[pos].cap=0, edge[pos].next=adj[b];
    adj[b]=pos;
    ++pos;
}

queue<int>q;
bool in[MAXN];
void spfa()
{
    for(int i=0;i<t;++i)d[i]=inf;
    d[t]=0, q.push(t);
    int u, v;
    while(!q.empty())
    {
        u=q.front(), in[u]=0;
        q.pop();
        for(int p=adj[u];p!=-1;p=edge[p].next)
            if(edge[p^1].cap>0)
                if(d[(v=edge[p].v)]>d[u]+1)
                {
                    d[v]=d[u]+1;
                    if(!in[v])in[v]=1, q.push(v);
                }
    }
}

int aug(int u,int augco)
{
    if(u==t)return augco;
    int augc=augco, delta, v, mind=End-1;
    for(int p=adj[u];p!=-1&&augc;p=edge[p].next)
        if(edge[p].cap>0)
        {
            if(d[(v=edge[p].v)]+1==d[u])
            {
                delta=aug(v,min(augc,edge[p].cap));
                edge[p].cap-=delta, edge[p^1].cap+=delta, augc-=delta;
                if(d[s]>=End)break;
            }
            mind=min(mind,d[v]);
        }
    if(augco==augc)
    {
        --vd[d[u]];
        if(!vd[d[u]])d[s]=End;
        d[u]=mind+1;
        ++vd[d[u]];
    }
    return augco-augc;
}

int isap(int a,int b,int c)
{
    s=a, t=b, End=c;
    spfa();
    memset(vd,0,sizeof vd);
    for(int i=0;i<=c;++i)
        if(d[i]!=inf)++vd[d[i]];
    int ans=0;
    while(d[s]<End)
        ans+=aug(s,inf);
    return ans;
}

bool check1(int mid)
{
    memset(adj,-1,sizeof adj), pos=0;
    for(int i=0;i<m;++i)
        add(a[i],b[i],min(mid,c[i]));
    return isap(S,T,n)==maxflow;
}

LL solve1()
{
    int l=0, r=maxc, mid;
    LL ans=0;
    while(l<=r)
    {
        mid=(l+r)/2;
        if(check1(mid))
            ans=mid, r=mid-1;
        else l=mid+1;
    }
    return ans*sum;
}

bool check2(int mid)
{
    memset(adj,-1,sizeof adj), memset(du,0,sizeof du);
    pos=0;

    for(int i=0;i<m;++i)
    {
        if(c[i]<mid)return 0;
        add(a[i],b[i],c[i]-mid);
        du[a[i]]-=mid, du[b[i]]+=mid;
    }

    s=n, t=n+1;
    for(int i=0;i<n;++i)
        if(du[i]>0)add(s,i,du[i]);
        else if(du[i]<0)add(i,t,-du[i]);
    add(T,S,inf);
    isap(s,t,t+1);
    for(int p=adj[s];p!=-1;p=edge[p].next)
        if(edge[p].cap)return 0;
    return isap(S,T,t+1)==maxflow;
}

LL solve2()
{
    int l=0, r=maxc, mid;
    LL ans=0;
    while(l<=r)
    {
        mid=(l+r)/2;
        if(check2(mid))ans=mid, l=mid+1;
        else r=mid-1;
    }
    return ans*sum;
}

int main()
{
    int cas;
    scanf("%d",&cas);
    while(cas--)
    {
        scanf("%d%d%d%d%lld",&n,&m,&S,&T,&sum);

        memset(adj,-1,sizeof adj);
        maxc=0, pos=0;
        for(int i=0;i<m;++i)
        {
            scanf("%d%d%d",&a[i],&b[i],&c[i]);
            maxc=max(c[i],maxc);
            add(a[i],b[i],c[i]);
        }

        maxflow=isap(S,T,n);
        printf("%lld %lld\n",solve1(),solve2());
    }
    return 0;
}

你可能感兴趣的:(【上下界网络流】sgu194 zoj3229 sgu176 zoj1994 zoj3496)