有上下界的网络流专辑

来源:互联网 发布:java cs架构开发 编辑:程序博客网 时间:2024/04/29 18:06
 相对于一般的网络流,有上下界的网络流的某些边多出了流量下界的限制,如边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;}


原创粉丝点击