ZOJ 3229 Shoot the Bullet (有源汇有上下界的最大流)
Description
一位摄影师要在\(n\)天给\(m\)个女孩拍照,第\(x\)个女孩至少要拍\(G_x\)张,在第\(k\)天,摄影师有\(C_k\)个目标,\(T_{k1}\),\(T_{k2}\),\(\cdots\),\(T_{kCk}\),给目标\(T_{ki}\)拍的照片的数量要在\([L_{ki},R_{ki}]\)的范围内,第\(k\)天摄影师最多只能拍\(D_k\)张照片。问在不违反这些限制条件的情况下,摄影师最多可以拍的照片数量。
Input
多组用例,每组用例第一行给出两个整数\(n\)和\(m\),表示拍照的天数和女孩的数量,第二行给出\(m\)个数,第\(i\)个数\(g_i\)表示给每个女孩至少要拍的照片数量,对于每一天,第一行给出两个整数\(c\)和\(d\),表示目标的数量和这天最多拍的照片数,接下来的\(c\)行,每行给出三个数\(t\),\(l\),\(r\),表示对编号\(t\)的女孩拍的照片数量要在\([l,r]\)的范围内。
\(1 \leqslant n \leqslant 365\),\(1 \leqslant m \leqslant 1000\),\(1 \leqslant c \leqslant 100\),\(0 \leqslant d \leqslant 30000\),\(0 \leqslant t < m\),\(0 \leqslant l \leqslant r \leqslant 100\)。
Output
对于每组用例,如果有可行方案,第一行输出最多能拍的照片数量,接下来每行输出每一天给每一个女孩拍的照片数量。如果有多组最优解,输出任意一组即可。如果没有可行方案,输出"-1"。
Sample Input
2 3
12 12 12
3 18
0 3 9
1 3 9
2 3 9
3 18
0 0 3
1 3 6
2 6 9
Sample Output
36
9
6
3
3
6
9
Solution
有源汇带上下界的最大流问题。建图,左侧\(n\)个点代表\(n\)天,右侧\(m\)个点代表\(m\)个女孩,若第\(i\)天要给第\(j\)个女孩拍照片,照片数量要在\([l,r]\)范围内,则代表第\(i\)天的点到第\(j\)天的点有一条容量范围位\([l,r]\)的边。添加源点\(s\)和汇点\(t\),源点\(s\)与代表\(n\)天的点之间建容量范围为\([0,d]\)的边,代表\(m\)个女孩的点与汇点\(t\)之间有容量范围为\([g,\infty]\)的边。这样一个有源汇带上下界的流网络就建好了。
下面就是有源汇带上下界的最大流问题的解决方法,首先判断是否有可行流,如果有可行流再寻找最大流。
-
判断可行流:从汇点\(t\)到源点建一条容量为无穷大的边把原来的网络变为无源汇的网络。对于每个结点\(i\),\(in[i]\)表示必须要流入\(i\)点的流量,\(out[i]\)表示必须要流出\(i\)点的流量,令\(du[i]=in[i]-out[i]\),表示这个点净的流入或流出量。另外建立一个超集源点\(ss\)和超级汇点\(tt\),遍历每个结点\(i\),如果\(du[i]>0\),就建一条从\(ss\)到i的容量为\(du[i]\)的边,如果\(du[i]<0\),就建一条从\(i\)到\(tt\)的容量为\(-du[i]\)的边,原来的每条容量范围为\([l,r]\)的边就改为容量为\([0,r-l]\)的边,这样就变成了源点为\(ss\)汇点为\(tt\)普通的流网络。从\(ss\)到\(tt\)跑最大流,如果满流,说明左右的必要弧已经流满,存在可行流。
-
求最大流:删掉超级源点\(ss\)和超级汇点\(tt\),从原来的源点\(s\)到原来的汇点\(t\)跑一遍最大流,得到的答案就是原来有源汇带上下界的网络的最大流。因为第一遍跑的必要流的流量存储在从t到s的反向弧中,在从\(s\)到\(t\)跑最大流时,\(s\)到\(t\)的边中流过了必要流,整体的网络中流过了自由流,因此整体的最大流就是原网络的最大流。
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 2e3 + 10;
const int M = 8e4 + 10;
struct Edge
{
int to, c, next;
Edge() {}
Edge(int to, int c, int next) : to(to), c(c), next(next) {}
} edge[M];
int adj[N], tot;
void init()
{
memset(adj, -1, sizeof(adj));
tot = 0;
}
void add(int u, int v, int c)
{
edge[tot] = Edge(v, c, adj[u]);
adj[u] = tot++;
edge[tot] = Edge(u, 0, adj[v]);
adj[v] = tot++;
}
int level[N];
queue<int> q;
bool bfs(int s, int t)
{
while (!q.empty()) q.pop();
memset(level, -1, sizeof(level));
level[s] = 0; q.push(s);
while (!q.empty())
{
int u = q.front(); q.pop();
for (int i = adj[u]; i != -1; i = edge[i].next)
{
Edge &e = edge[i];
if (e.c && level[e.to] == -1)
{
level[e.to] = level[u] + 1;
if (e.to == t) return true;
q.push(e.to);
}
}
}
return false;
}
int cur[N];
int dfs(int u, int t, int flow)
{
if (u == t) return flow;
for (int &i = cur[u]; i != -1; i = edge[i].next)
{
Edge &e = edge[i];
if (e.c && level[e.to] > level[u])
{
int f = dfs(e.to, t, min(flow, e.c));
if (f)
{
e.c -= f;
edge[i ^ 1].c += f;
return f;
}
}
}
return 0;
}
int dinic(int s, int t)
{
int flow = 0;
while (bfs(s, t))
{
memcpy(cur, adj, sizeof(adj));
int f;
while (f = dfs(s, t, INF)) flow += f;
}
return flow;
}
int du[N], lb[N], c[400], id[400][110];
int main()
{
int n, m;
while (~scanf("%d%d", &n, &m))
{
init();
memset(du, 0, sizeof(du));
int s = 0, t = n + m + 1;
for (int i = 1; i <= m; i++)
{
int g;
scanf("%d", &g);
du[t] += g; du[n + i] -= g;
add(n + i, t, INF);
}
for (int i = 1; i <= n; i++)
{
int d;
scanf("%d%d", &c[i], &d);
add(s, i, d);
for (int j = 1; j <= c[i]; j++)
{
int t, l, r;
scanf("%d%d%d", &t, &l, &r);
t++;
du[n + t] += l; du[i] -= l;
id[i][j] = tot;
lb[tot] = l;
add(i, n + t, r - l);
}
}
add(t, s, INF);
int ss = n + m + 2, tt = n + m + 3;
int sum = 0;
for (int i = 0; i <= n + m + 1; i++)
{
if (du[i] > 0) add(ss, i, du[i]), sum += du[i];
else if (du[i] < 0) add(i, tt, -du[i]);
}
int ans = dinic(ss, tt);
if (ans < sum) { printf("-1\n\n"); continue; }
adj[ss] = -1; adj[tt] = -1;
ans = dinic(s, t);
printf("%d\n", ans);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= c[i]; j++)
printf("%d\n", edge[id[i][j] + 1].c + lb[id[i][j]]);
printf("\n");
}
return 0;
}