相对于一般的网络流,有上下界的网络流的某些边多出了流量下界的限制,如边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;
}