有上下界的网络流专辑

 相对于一般的网络流,有上下界的网络流的某些边多出了流量下界的限制,如边u->v ,上下界为high、low,如果有流经过这条边,这个流必须在[low,high]这个区间内。这类题目主要要求解决下面三个问题,“有源汇、无源汇的可行流”、“有源汇的最大流”、“有源汇的最小流”,注意这里所说的源汇是原网络中的源汇,分别记为s、t。
  这类题目的难点在于下界的限制很难处理,我们将所有有下界限制的边中分离出“必须边”来单独做考虑,所谓“必须边”就是必须要满流的边,如上述的边 ,我们可以拆成两条边,第一条是“必须边”,流量上限为low,第二条是一般的边,流量上限为high-low。
  怎样对必须边进行考虑呢?我们建立新的超级源汇,记为ss、tt,对于所有必须边再次进行拆边处理,如上述必须边u->v ,应该拆成ss->v、u->tt两条流量上限都为low的边(这里很重要,应该要好好理解,我当时学的时候是不断画图来体会这种思想的)。
 
  经过上述步骤,我们所需要的网络已经建立好了,依次来分析那三个问题。(这里所提到的所有变量均为上面所定义的)
 
一、有源汇、无源汇的可行流。
  求可行流,其实就是问是否存在一个方案可以使所有必须边都满流。对于有源汇的网络,我们可以添加一条边t->s,流量上限为INF,这样就变成了无源汇的网络。对于无源汇的网络,只要对ss到tt求一次最大流,若所有必须边都满流,则有可行解,若要求打印方案,只需将非必须边中流过的流量加上流量下界(必须边已满流)。
 
二、有源汇的最大流
  这里的最大流,前提是使所有必须边满流,再要求从s到t的流量最大(注意,这里所求的最大流是原网络的最大流,而我们求ss到tt的最大流只是用于判断是否能使所有必须边满流)。首先判断所有必须边是否满流,这里和问题一中提到的方法一样,注意这里是有源汇的网络。然后直接对残留网络求一次从s到t的最大流,这个最大流就是最终答案。
 
三、有源汇的最小流
  和问题二相反,我们要使所有必须边满流的情况下,要求从s到t的流量最小。这个问题比上面的问题都要复杂,分三个步骤。
1、对ss到tt求一次最大流,记为f1。(在有源汇的情况下,先使整个网络趋向必须边尽量满足的情况)
2、添加一条边t->s,流量上限为INF,这条边记为p。(构造无源汇网络)
3、对ss到tt再次求最大流,记为f2。(要判断可行,必须先构造无源汇网络流,因此要再次求最大流)
 
如果所有必须边都满流,证明存在可行解,原图的最小流为“流经<边p>的流量”(原图已构造成无源汇网络,对于s同样满足入流 == 出流,只有新添加的边流向s,而s的出流就是原图的最小流)。

 

  这类题目的建模难度都很小,几乎可以一眼看出网络流模型,主要是构图、求解方面的问题,这里贴出我找到的仅有的6道题目的核心代码,所有求最大流都是用Dinic,这里为了尽量简洁略去Dinic的实现。然后题意、类型、题解也不写了,认真读完上面的讲解已经足够解决以下问题。

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=2314      ZOJ 2314

// 100 Ms
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
const int INF = 1e9;
const int N = 210;
const int M = 10*N*N;
 
struct Data
{
    int x, y, f, next;
} edge[M];
int inx;
int node[N], level[N];
 
void addedge(int u, int v, int f)
{
    edge[inx].x = u, edge[inx].y = v, edge[inx].f = f;
    edge[inx].next = node[u], node[u] = inx++;
    edge[inx].x = v, edge[inx].y = u, edge[inx].f = 0;
    edge[inx].next = node[v], node[v] = inx++;
}
 
int main()
{
    int Case, n, m;
    scanf("%d", &Case);
    while (Case--)
    {
        inx = 0;
        memset(node, -1, sizeof(node));
 
        scanf("%d %d", &n, &m);
        int ss = 0, tt = n+1;
        int a, b, c, d;
        queue< pair<int, int> > que;
        int sum = 0;
        while (m--)
        {
            scanf("%d %d %d %d", &a, &b, &c, &d);
            addedge(a, tt, c);
            addedge(ss, b, c);
            sum += c;
 
            que.push(make_pair(c, inx));    // 记录每条边的下限、非必须边的下标
            addedge(a, b, d-c);
        }
 
        if (sum != Dinic(ss, tt))
            printf("NO\n");
        else
        {
            printf("YES\n");
            while ( !que.empty() )
            {
                int down = que.front().first;
                int p = que.front().second;
                printf("%d\n", down + edge[p^1].f);
                que.pop();
            }
        }
        if (Case) printf("\n");
    }
    return 0;
}

http://acm.sgu.ru/problem.php?contest=0&problem=242       SGU 242

// 31 Ms
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
const int INF = 1e9;
const int N = 510;
const int M = N*N;
 
struct Data
{
    int x, y, f, next;
} edge[M];
int inx;
int node[N], level[N];
 
void addedge(int u, int v, int f)
{
    edge[inx].x = u, edge[inx].y = v, edge[inx].f = f;
    edge[inx].next = node[u], node[u] = inx++;
    edge[inx].x = v, edge[inx].y = u, edge[inx].f = 0;
    edge[inx].next = node[v], node[v] = inx++;
}
 
int main()
{
    int n, m;
    while (scanf("%d %d", &n, &m) != EOF)
    {
        inx = 0;
        memset(node, -1, sizeof(node));
 
        int s = 0, t = n+m+1;
        int ss = n+m+2, tt = ss+1;
        int sum = 0;
        for (int i = 1; i <= n; i++)
            addedge(s, i, 1);   // 每个学生只能去一个学校,所以流量上限为1
        for (int i = n+1; i <= n+m; i++)
        {
            sum += 2;
            addedge(i, tt, 2);  // 最少要有两个学生才能去第i间学校(下界)
            addedge(ss, t, 2);
            addedge(i, t, INF); // 最多能有无数人去这间学校(上界)
        }
 
        int k, tmp;
        for (int i = 1; i <= n; i++)
        {
            scanf("%d", &k);
            while (k--)
            {
                scanf("%d", &tmp);
                addedge(i, n+tmp, 1);   // 学生和想去的学校间连接流量上限为1的边
            }
        }
        addedge(t, s, INF);
        if (sum != Dinic(ss, tt))
            printf("NO\n");
        else
        {
            printf("YES\n");
            queue<int> que[210];
            for (int i = 1; i <= n; i++)
            {
                for (int ip = node[i]; ip != -1; ip = edge[ip].next)
                {
                    Data &v = edge[ip];
                    if (v.y > n && v.y <= n+m && edge[ip^1].f > 0)
                    {   // 判断是否代表学校的节点、判断是否有流经过
                        que[v.y-n].push(i);
                        break;
                    }
                }
            }
            for (int i = 1; i <= m; i++)
            {
                printf("%d", que[i].size());
                while ( !que[i].empty() )
                {
                    printf(" %d", que[i].front());
                    que[i].pop();
                }
                printf("\n");
            }
        }
    }
    return 0;
}

http://acm.hdu.edu.cn/showproblem.php?pid=3157       HDU 3157

// 15 Ms
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
const int INF = 1e9;
const int N = 110;
const int M = N*N;
 
struct Data
{
    int x, y, f, next;
} edge[M];
int inx;
int node[N], level[N];
 
void addedge(int u, int v, int f)
{
    edge[inx].x = u, edge[inx].y = v, edge[inx].f = f;
    edge[inx].next = node[u], node[u] = inx++;
    edge[inx].x = v, edge[inx].y = u, edge[inx].f = 0;
    edge[inx].next = node[v], node[v] = inx++;
}
 
int main()
{
    int n, m;
    while (scanf("%d %d", &n, &m) != EOF)
    {
        if (n+m == 0) break;
        inx = 0;
        memset(node, -1, sizeof(node));
 
        int s = n+1, t = n+2;
        int ss = n+3, tt = n+4;
        char a[5], b[5];
        int c, sum = 0;
        while (m--)
        {
            scanf("%s %s %d", a, b, &c);
            int u, v;
            if (a[0] == '+') u = s;
            else sscanf(a, "%d", &u);   // 注意这里读取信息
            if (b[0] == '-') v = t;
            else sscanf(b, "%d", &v);
            addedge(u, tt, c);
            addedge(ss, v, c);
            sum += c;
            addedge(u, v, INF);         // 非必须边流量上限为无穷
        }
 
        int f1 = Dinic(ss, tt);         // 求最小流
        int p = inx;
        addedge(t, s, INF);
        int f2 = Dinic(ss, tt);
        if (f1+f2 != sum)
            printf("impossible\n");
        else
            printf("%d\n", edge[p^1].f);
    }
    return 0;
}

http://acm.sgu.ru/problem.php?contest=0&problem=176        SGU 176

// 46 Ms
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
const int INF = 1e9;
const int N = 110;
 
struct Data
{
    int x, y, f, next;
} edge[N*N];
int inx;
int node[N], level[N];
 
void addedge(int u, int v, int f)
{
    edge[inx].x = u, edge[inx].y = v, edge[inx].f = f;
    edge[inx].next = node[u], node[u] = inx++;
    edge[inx].x = v, edge[inx].y = u, edge[inx].f = 0;
    edge[inx].next = node[v], node[v] = inx++;
}
 
int main()
{
    int n, m;
    while (scanf("%d %d", &n, &m) != EOF)
    {
        inx = 0;
        memset(node, -1, sizeof(node));
 
        // 构图
        int ss = n+1, tt = n+2;
        int a, b, c, d, sum = 0;      // 记录必须边总流量
        queue< pair<int, int> > que;  // 记录边的顺序、下标
        while (m--)
        {
            scanf("%d %d %d %d", &a, &b, &c, &d);
            if (d == 1)   // 必须边
            {
                sum += c;
                que.push(make_pair(1, c));
                addedge(a, tt, c);
                addedge(ss, b, c);
            }
            else
            {
                que.push(make_pair(0, inx));
                addedge(a, b, c);
            }
        }
 
        // 核心代码,求有下界的最小流
        int flow1 = Dinic(ss, tt);
        int pos = inx; addedge(n, 1, INF);
        int flow2 = Dinic(ss, tt);
 
        if (flow1+flow2 != sum)
        {
            printf("Impossible\n");
            continue;
        }
 
        printf("%d\n", edge[pos^1].f);
        while ( !que.empty() )
        {
            if (que.front().first == 1)
                printf("%d ", que.front().second);
            else
            {
                int t = que.front().second;
                printf("%d ", edge[t^1].f);
            }
            que.pop();
        }
        printf("\n");
    }
    return 0;
}

http://poj.org/problem?id=2396        POJ 2396

// 79 Ms
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <stack>
using namespace std;
 
const int N = 300;
const int M = 100000;
const int INF = 1e9;
 
int low[N][N], high[N][N];
int column[N], row[N];
 
struct Data
{
    int x, y, f, next;
} edge[M];
int inx;
int node[N], level[N];
 
void addedge(int u, int v, int f)
{
    edge[inx].x = u, edge[inx].y = v, edge[inx].f = f;
    edge[inx].next = node[u], node[u] = inx++;
    edge[inx].x = v, edge[inx].y = u, edge[inx].f = 0;
    edge[inx].next = node[v], node[v] = inx++;
}
 
void Limit_flow(int n, int m, int ss, int tt, int sum)
{
    int flow = Dinic(ss, tt);     // 计算超级源汇间的最大流
    if (flow != sum)              // 判断最大流是否等于必须边的流总量
        printf("IMPOSSIBLE\n");   // 满足则源汇间存在可行流
    else
    {
        for (int i = 1; i <= n; i++)   // 计算可行流的实际方案
        {
            for (int ip = node[i]; ip != -1; ip = edge[ip].next)
            {
                Data &v = edge[ip];
                if (n+1 <= v.y && v.y <= n+m)       // 判断是否列节点
                    low[i][v.y-n] += edge[ip^1].f;  // 必须边的流量 + 额外通过的流量
            }
        }
        for (int i = 1; i <= n; i++)   // 打印可行方案
        {
            for (int j = 1; j <= m; j++)
                printf(j == 1? "%d":" %d", low[i][j]);
            printf("\n");
        }
    }
}
 
bool Construct(int n, int m, int ss, int tt)
{
    int sum = 0;                    // 统计必须边的流总量
    int s = n+m+1, t = s+1;         // 原图的源、汇
    addedge(t, s, INF);             // 构造无源汇图
    for (int i = 1; i <= n; i++)    // 从源点向所有行节点引流
    {
        sum += column[i];
        addedge(s, tt, column[i]);  // 拆边,原来是 s -> i 节点的边
        addedge(ss, i, column[i]);  // 拆分成 s -> tt,ss -> i 两条边
    }
    for (int j = 1; j <= m; j++)    // 从所有列节点向汇点引流
    {
        sum += row[j];
        addedge(n+j, tt, row[j]);
        addedge(ss, t, row[j]);
    }
    for (int i = 1; i <= n; i++)           // 行节点 -> 列节点 引流
    {                                      // 分离必须边(拆边)
        for (int j = 1; j <= m; j++)
        {
            if (low[i][j] > high[i][j])    // 边界流量限制不合法
                return 0;
            sum += low[i][j];
            addedge(i, tt, low[i][j]);     // 必须边的流量为下界low[i][j]
            addedge(ss, n+j, low[i][j]);   // 分离成 i->tt, ss->n+j
 
            addedge(i, n+j, high[i][j]-low[i][j]);
        }                                  // 将剩余流量在原节点补充 i->n+j
    }
    Limit_flow(n, m, ss, tt, sum);
    return 1;
}
 
void Ini(int x, int y, char ch, int weight)  // 按要求缩小每条边的上、下界
{
    if (ch == '=')
    {
        low[x][y] = max(low[x][y], weight);
        high[x][y] = min(high[x][y], weight);
    }
    else if (ch == '<')
        high[x][y] = min(high[x][y], weight-1);
    else
        low[x][y] = max(low[x][y], weight+1);
}
 
int main()
{
    int Case, n, m, k;
    scanf("%d", &Case);
    while (Case--)
    {
        // 初始化邻接表
        inx = 0;
        memset(node, -1, sizeof(node));
 
        scanf("%d %d", &n, &m);
        for (int i = 1; i <= n; i++)
            scanf("%d", &column[i]);
        for (int j = 1; j <= m; j++)
            scanf("%d", &row[j]);
 
        for (int i = 1; i <= n; i++)   // 初始化每条边的上下界
        {
            for (int j = 1; j <= m; j++)
            {
                low[i][j] = 0;
                high[i][j] = INF;
            }
        }
 
        // 读入限制条件
        int a, b, c;
        char op[2];
        scanf("%d", &k);
        while (k--)
        {
            scanf("%d %d %s %d", &a, &b, op, &c);
            if (a == 0 && b == 0)
                for (int i = 1; i <= n; i++)
                    for (int j = 1; j <= m; j++)
                        Ini(i, j, op[0], c);
            else if (a == 0)
                for (int i = 1; i <= n; i++)
                    Ini(i, b, op[0], c);
            else if (b == 0)
                for (int j = 1; j <= m; j++)
                    Ini(a, j, op[0], c);
            else
                Ini(a, b, op[0], c);
        }
 
        // 构图,行节点编号为1~n,列节点编号为n+1~n+m,源、汇为n+m+1,n+m+2
        int ss = n+m+3, tt = ss+1;              // 超级源、汇
        if (Construct(n, m, ss, tt) == 0)  // 构造原图
            printf("IMPOSSIBLE\n");
        if (k)
            printf("\n");
    }
    return 0;
}

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3229        ZOJ 3229

// 1790 Ms
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
const int INF = 1e9;
const int N = 2010;
const int M = 500*N;   // N*N 会MLE,100*N 会RE,500*N 刚好AC
 
struct Data
{
    int x, y, f, next;
} edge[M];
int inx;
int node[N], level[N];
 
void addedge(int u, int v, int f)
{
    edge[inx].x = u, edge[inx].y = v, edge[inx].f = f;
    edge[inx].next = node[u], node[u] = inx++;
    edge[inx].x = v, edge[inx].y = u, edge[inx].f = 0;
    edge[inx].next = node[v], node[v] = inx++;
}
 
int main()
{
    int n, m;
    while (scanf("%d %d", &n, &m) != EOF)
    {
        inx = 0;
        memset(node, -1, sizeof(node));
 
        int s = n+m+10, t = s+1;
        int ss = t+1, tt = ss+1;
        int sum = 0;
        queue< pair<int, int> > que;
 
        int a, b, c;
        for (int i = 0; i < m; i++)
        {
            scanf("%d", &a);
            addedge(i, tt, a);
            addedge(ss, t, a);
            sum += a;
            addedge(i, t, INF);           // 每个女孩拍照下限为ai,上限为INF
        }
        for (int i = 1; i <= n; i++)
        {
            int C, D;
            scanf("%d %d", &C, &D);
            addedge(s, m+i, D);           // 每天最多拍照数为D
            for (int j = 1; j <= C; j++)
            {
                scanf("%d %d %d", &a, &b, &c);
                addedge(m+i, tt, b);
                addedge(ss, a, b);
                sum += b;
                que.push(make_pair(b, inx));
                addedge(m+i, a, c-b);         // 该天a女孩拍照下限为b,上限为c
            }
        }
 
        addedge(t, s, INF);                // 求有上下界的最大流
        int flow = Dinic(ss, tt);
 
        if (sum != flow)
            printf("-1\n");
        else
        {
            printf("%d\n", Dinic(s, t));
            while ( !que.empty() )
            {
                int down = que.front().first;
                int p = que.front().second;
                printf("%d\n", down + edge[p^1].f);
                que.pop();
            }
        }
        printf("\n");
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值