有上下界的网络流

有上下界的网络流具体可以分为以下三种:
1.无源汇有上下界可行流

例题:ZOJ - 2314

题意:有 n n n 个节点,且有 m m m 条水管,每条水管的流向为单向,且每条水管的流量有限制,第 i i i 条水管的流量 f i ( l i ≤ f i ≤ c i ) f_i(l_i \leq f_i \leq c_i) fi(lifici) ,问是否存在一种流量分配,使得这m条水管的流量满足条件,如果存在,则输出每条水管的流量。

思路:一般的网络流算法,对于流量是只有上界的,下界默认为0,那么具有非0下界,我们需要构造一个新的图使得其下界为0。
建图方法:对于第 i i i 条边,上下界为 c i , l i c_i,l_i cili ,则新的边上下界为 c i − l i c_i-l_i cili ,0。
设置一个数组 d u [ i ] du[i] du[i] 来记录每个节点的流量。
d u [ i ] = i n [ i ] − o u t [ i ] du[i] = in[i]-out[i] du[i]=in[i]out[i]
( i n [ i ] in[i] in[i]:节点 i i i 的所有入流的 l [ i ] l[i] l[i] 的和, o u t [ i ] out[i] out[i]:节点 i i i 的所有出流的 l [ i ] l[i] l[i] 的和)
同时再增加一个超级源点 s t st st ,以及一个超级汇点 e n en en
对于除了超级超级源点和汇点外的其他点:
d u [ i ] > 0 du[i] > 0 du[i]>0 s t st st i i i 连一条上界为 d u [ i ] du[i] du[i] 的边。
d u [ i ] &lt; 0 du[i] &lt; 0 du[i]<0 i i i e n en en 连一条上界为 − d u [ i ] -du[i] du[i] 的边。
到此整个新图就建好了,在新图上跑最大流即可,若满流则说明有解。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<queue>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f
#define NUM 205
#define ffor(i,d,u) for(int i=(d);i<=(u);++i)
#define _ffor(i,u,d) for(int i=(u);i>=(d);--i)
#define mst(array,Num,Kind,Count) memset(array,Num,sizeof(Kind)*(Count))
template <typename T>
void read(T& x)
{
    x=0;
    char c;T t=1;
    while(((c=getchar())<'0'||c>'9')&&c!='-');
    if(c=='-'){t=-1;c=getchar();}
    do(x*=10)+=(c-'0');while((c=getchar())>='0'&&c<='9');
    x*=t;
}
template <typename T>
void write(T x)
{
    int len=0;char c[21];
    if(x<0)putchar('-'),x*=(-1);
    do{++len;c[len]=(x%10)+'0';}while(x/=10);
    _ffor(i,len,1)putchar(c[i]);
}
int n, m, edge_num;
struct edge
{
    int to, next, w, f;
} e[NUM * NUM * 2];
int du[NUM], head[NUM], num[NUM], depth[NUM];
int low[NUM * NUM * 2], sum_du;
inline void add_edge(const int &x, const int &y, const int &c)
{
    e[++edge_num] = edge{y, head[x], c, 0}, head[x] = edge_num;
}
inline void bfs()
{
    int x, y;
    mst(num, 0, int, n + 2);
    mst(depth, INF, int, n + 2);
    queue<int> q;
    q.push(n + 1), ++num[depth[n + 1] = 1];
    while (!q.empty())
    {
        x = q.front(), q.pop();
        for (int i = head[x]; i != -1; i = e[i].next)
        {
            y = e[i].to;
            if (depth[y] != INF || e[i].w != 0)
                continue;
            ++num[depth[y] = depth[x] + 1];
            q.push(y);
        }
    }
}
int dfs(const int &vertex, int minx)
{
    if (minx == 0 || vertex == n + 1)
        return minx;
    int f, x, flow = 0;
    for (int i = head[vertex]; i != -1; i = e[i].next)
    {
        x = e[i].to;
        if (depth[vertex] - 1 == depth[x] && (f = dfs(x, min(minx, e[i].w))) != 0)
        {
            minx -= f, flow += f;
            e[i].w -= f, e[i].f += f;
            e[i ^ 1].w += f, e[i ^ 1].f -= f;
            if (minx == 0)
                return flow;
        }
    }
    if (!(--num[depth[vertex]]))
        depth[0] = n + 3;
    ++num[++depth[vertex]];
    return flow;
}
inline bool ISAP()
{
    int maxflow = 0;
    bfs();
    while (depth[0] <= n + 2)
        maxflow += dfs(0, INF);
    return maxflow == sum_du;
}
void AC()
{
    int l, c, x, y;
    int T;
    read(T);
    while (T--)
    {
        read(n), read(m);
        mst(head, -1, int, n + 2), mst(du, 0, int, n + 2), edge_num = -1, sum_du = 0;
        ffor(i, 1, m)
        {
            read(x), read(y), read(l), read(c);
            add_edge(x, y, c - l);
            add_edge(y, x, 0);//反向边,无向图的上界和正向边相同,有向图的为0
            du[y] += l;
            du[x] -= l;
            low[i] = l;
        }
        ffor(i, 1, n)//加附加边
        {
            if (du[i] < 0)
                add_edge(i, n + 1, -du[i]), add_edge(n + 1, i, 0);
            else if (du[i] > 0)
            {
                add_edge(0, i, du[i]), add_edge(i, 0, 0);
                sum_du += du[i];
            }
        }
        if (ISAP())
        {
            puts("YES");
            ffor(i, 1, m) write(low[i] + e[(i - 1) << 1].f), putchar('\n');
        }
        else
            puts("NO");
    }
}
int main()
{
    AC();
    return 0;
}
2.有源汇有上下界最大流

例题:ZOJ - 3229

题意:给 m m m 个女生拍照,每个女生至少要拍 G i G_i Gi 张照片,一共有 n n n 天的时间,第 i i i 天有 c i c_i ci 位女生可以拍照,且当天一共只能拍 d i d_i di 张照片,而第 c i , j c_{i,j} ci,j 位女生当天至少要拍 l j l_j lj 张,最多只能拍 r j r_j rj 张,问如何分配能够满足这 m m m 个女生的拍照要求,若可以,输出能够拍的照片数最多的拍照方案。

思路:新增一个源点 s s s ,一个汇点 t t t ,源点到第 i i i 天连一条容量为 d i d_i di 的边,第 i i i 个女生到汇点连一条下界为 G i G_i Gi ,上界为无穷大的边。可以建造新图转化为无源汇有上下界的问题求解。
建图方法:由汇点到源点连一条上界为无穷大,下界为零的边,这样整个图就是个无源汇有上下界的问题,之后设一个超级源点 s t st st ,超级汇点 e n en en,再按无源汇有上下界的求解方法建图,即可判断是否有可行解。
判断完是否有可行解之后,由于残余网络中除了超级源点与汇点外的边,仍可能存在增广路,所以需要排除超级源点与汇点后再求解一次最大流。最后所得即为最大解。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<queue>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f
#define NUM 1380
#define ffor(i,d,u) for(int i=(d);i<=(u);++i)
#define _ffor(i,u,d) for(int i=(u);i>=(d);--i)
#define mst(array,Num,Kind,Count) memset(array,Num,sizeof(Kind)*(Count))
template <typename T>
void read(T& x)
{
    x=0;
    char c;T t=1;
    while(((c=getchar())<'0'||c>'9')&&c!='-');
    if(c=='-'){t=-1;c=getchar();}
    do(x*=10)+=(c-'0');while((c=getchar())>='0'&&c<='9');
    x*=t;
}
template <typename T>
void write(T x)
{
    int len=0;char c[21];
    if(x<0)putchar('-'),x*=(-1);
    do{++len;c[len]=(x%10)+'0';}while(x/=10);
    _ffor(i,len,1)putchar(c[i]);
}
int n, m, edge_num;//天数标号2~n+1,女生标号n+2~n+m+1
struct edge
{
    int to, next, w, f;
} e[NUM * 60];
int du[NUM], head[NUM], num[NUM], depth[NUM];
int low[NUM * 60], sum_du;
int Index[370][105];
inline void add_edge(const int &x, const int &y, const int &c)
{
    e[++edge_num] = edge{y, head[x], c, 0}, head[x] = edge_num;
}
inline void bfs(const int &s, const int &t)
{
    int x, y;
    mst(num, 0, int, t - s + 2), mst(depth, INF, int, t - s + 2);
    queue<int> q;
    q.push(t), ++num[depth[t] = 1];
    while (!q.empty())
    {
        x = q.front(), q.pop();
        for (int i = head[x]; i != -1; i = e[i].next)
        {
            y = e[i].to;
            if (depth[y] != INF || e[i].w < e[i].f)
                continue;
            ++num[depth[y] = depth[x] + 1];
            q.push(y);
        }
    }
}
int dfs(const int &s, const int &t, const int &vertex, int minx)
{
    if (minx == 0 || vertex == t)
        return minx;
    int f, x, flow = 0;
    for (int i = head[vertex]; i != -1; i = e[i].next)
    {
        x = e[i].to;
        if (depth[vertex] - 1 == depth[x] && (f = dfs(s, t, x, min(minx, e[i].w))) != 0)
        {
            minx -= f, flow += f;
            e[i].w -= f, e[i].f += f;
            e[i ^ 1].w += f, e[i ^ 1].f -= f;
            if (minx == 0)
                return flow;
        }
    }
    if (!(--num[depth[vertex]]))
        depth[s] = t - s + 2;
    ++num[++depth[vertex]];
    return flow;
}
inline int ISAP(const int &s, const int &t)
{
    int maxflow = 0;
    bfs(s, t);
    while (depth[s] <= t - s + 1)
        maxflow += dfs(s, t, s, INF);
    return maxflow;
}
void AC()
{
    int d, x, y, l, r;
    int flow;
    while (scanf("%d", &n) != EOF)
    {
        read(m);
        mst(head, -1, int, n + m + 4), mst(du, 0, int, n + m + 4), edge_num = -1, sum_du = 0;
        ffor(i, 1, m)
        {
            read(x);
            add_edge(1 + n + i, n + m + 2, INF - x), add_edge(n + m + 2, 1 + n + i, 0);
            du[n + m + 2] += x;
            du[1 + n + i] -= x;
        }
        ffor(i, 1, n)
        {
            read(Index[i][0]), read(d);
            x = 1 + i;
            add_edge(1, x, d), add_edge(x, 1, 0);
            ffor(j, 1, Index[i][0])
            {
                read(y), read(l), read(r);
                y += (2 + n);
                add_edge(x, y, r - l), low[Index[i][j] = edge_num] = l, add_edge(y, x, 0);
                du[y] += l;
                du[x] -= l;
            }
        }
        add_edge(n + m + 2, 1, INF), add_edge(1, n + m + 2, 0);
        ffor(i, 1, n + m + 2)
        {
            if (du[i] < 0)
                add_edge(i, n + m + 3, -du[i]), add_edge(n + m + 3, i, 0);
            else if (du[i] > 0)
            {
                add_edge(0, i, du[i]), add_edge(i, 0, 0);
                sum_du += du[i];
            }
        }
        flow = ISAP(0, n + m + 3);
        if (flow == sum_du)
        {
            head[0] = head[n + m + 3] = -1;
            flow = ISAP(1, n + m + 2);
            write(flow), putchar('\n');
            ffor(i, 1, n) ffor(j, 1, Index[i][0]) write(low[Index[i][j]] + e[Index[i][j]].f), putchar('\n');
        }
        else
            puts("-1");
        putchar('\n');
    }
}
int main()
{
    AC();
    return 0;
}
3.有源汇有上下界最小流

例题:Codeforces - ACMSGURU - 176

题意:一共有 n n n 个节点, m m m 条管道,每条管道为单向流通,整个图的起点为 1 1 1 ,终点为 n n n 。对于第 i i i 个管道,有一个 z i z_i zi 表示该管道的容量,且有一个 c i c_i ci ,若 c i = 1 c_i=1 ci=1 ,则该管道的流量下界 = = = 上界 c i c_i ci ,若 c i = 0 c_i=0 ci=0 ,则该管道下界为 0 0 0 。现在从节点 1 1 1 输入一定流量,问是否可以使整个图保证流通,若可以,则最小得往节点 1 1 1 输入多少流量。

思路:按照无源汇有上下界最大流建图,但是不要在初始源点和汇点之间连容量为无穷大的那条边。然后跑一遍最大流,然后就从初始汇点往源点连一条容量为无穷大的边,两次最大流的和就是答案,之后依旧按照无源汇的判断是否有可行流,以及求每条边流量。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<queue>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f
#define NUM 105
#define ffor(i,d,u) for(int i=(d);i<=(u);++i)
#define _ffor(i,u,d) for(int i=(u);i>=(d);--i)
#define mst(array,Num,Kind,Count) memset(array,Num,sizeof(Kind)*(Count))
template <typename T>
void read(T& x)
{
    x=0;
    char c;T t=1;
    while(((c=getchar())<'0'||c>'9')&&c!='-');
    if(c=='-'){t=-1;c=getchar();}
    do(x*=10)+=(c-'0');while((c=getchar())>='0'&&c<='9');
    x*=t;
}
template <typename T>
void write(T x)
{
    int len=0;char c[21];
    if(x<0)putchar('-'),x*=(-1);
    do{++len;c[len]=(x%10)+'0';}while(x/=10);
    _ffor(i,len,1)putchar(c[i]);
}
int n, m, edge_num;//天数标号2~n+1,女生标号n+2~n+m+1
struct edge
{
    int to, next, w, f;
} e[NUM * NUM * 2];
int du[NUM] = {}, head[NUM], num[NUM] = {}, depth[NUM];
int low[NUM * NUM * 2], sum_du;
inline void add_edge(const int &x, const int &y, const int &c)
{
    e[++edge_num] = edge{y, head[x], c, 0}, head[x] = edge_num;
}
inline void bfs(const int &s, const int &t)
{
    int x, y;
    mst(depth, INF, int, s + t + 1);
    queue<int> q;
    q.push(t), ++num[depth[t] = 1];
    while (!q.empty())
    {
        x = q.front(), q.pop();
        for (int i = head[x]; i != -1; i = e[i].next)
        {
            y = e[i].to;
            if (depth[y] != INF || e[i].w < e[i].f)
                continue;
            ++num[depth[y] = depth[x] + 1];
            q.push(y);
        }
    }
}
int dfs(const int &s, const int &t, const int &vertex, int minx)
{
    if (minx == 0 || vertex == t)
        return minx;
    int f, x, flow = 0;
    for (int i = head[vertex]; i != -1; i = e[i].next)
    {
        x = e[i].to;
        if (depth[vertex] - 1 == depth[x] && (f = dfs(s, t, x, min(minx, e[i].w))) != 0)
        {
            minx -= f, flow += f;
            e[i].w -= f, e[i].f += f;
            e[i ^ 1].w += f, e[i ^ 1].f -= f;
            if (minx == 0)
                return flow;
        }
    }
    if (!(--num[depth[vertex]]))
        depth[s] = t - s + 2;
    ++num[++depth[vertex]];
    return flow;
}
inline int ISAP(const int &s, const int &t)
{
    int maxflow = 0;
    bfs(s, t);
    while (depth[s] <= t - s + 1)
        maxflow += dfs(s, t, s, INF);
    return maxflow;
}
void AC()
{
    int x, y, z, c;
    int flow;
    read(n), read(m);
    mst(head, -1, int, n + 2), edge_num = -1, sum_du = 0;
    ffor(i, 1, m)
    {
        read(x), read(y), read(z), read(c);
        c = ((c == 1) ? z : 0);
        add_edge(x, y, z - c), add_edge(y, x, 0);
        low[i] = c;
        du[y] += c;
        du[x] -= c;
    }
    ffor(i, 1, n)
    {
        if (du[i] < 0)
            add_edge(i, n + 1, -du[i]), add_edge(n + 1, i, 0);
        else if (du[i] > 0)
        {
            add_edge(0, i, du[i]), add_edge(i, 0, 0);
            sum_du += du[i];
        }
    }
    flow = ISAP(0, n + 1);
    add_edge(n, 1, INF), add_edge(1, n, 0);
    flow += ISAP(0, n + 1);
    if (flow == sum_du)
    {
        write(e[edge_num ^ 1].f), putchar('\n');
        ffor(i, 1, m) write(low[i] + e[(i - 1) << 1].f), putchar(' ');
        putchar('\n');
    }
    else
        puts("Impossible");
}
int main()
{
    AC();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值