题意:
数据范围:
Solution
题目要求的是一个最大值最小,那么可以考虑二分答案。
把题目的式子简单变一下:
∣
∑
A
−
B
∣
≤
m
i
d
|\sum A-B|\leq mid
∣∑A−B∣≤mid
A
−
m
i
d
≤
B
≤
A
+
m
i
d
A-mid\leq B \leq A+mid
A−mid≤B≤A+mid
也就是说
B
B
B的每一行每一列的和都要在一个范围之内,
B
B
B的每个数值又要在
L
,
R
L,R
L,R之内,于是我们可以算出每一行的上下界,计
s
i
s_i
si表示第
i
i
i行所有
A
A
A的和,那么下界为
m
a
x
(
s
i
−
m
i
d
,
L
∗
m
)
max(s_i-mid,L*m)
max(si−mid,L∗m),因为至少有
m
m
m个
L
L
L,上界为
m
i
n
(
s
i
+
m
i
d
,
R
∗
m
)
min(s_i+mid,R*m)
min(si+mid,R∗m),因为至多有
m
m
m个
R
R
R,我们将每一行的下界累加,上界累加。每一列也这样做一遍。如果最后行的总上下界与列的总上下界有交集,那么就存在解,第一问就做出来了。
问题在于,如何输出可行方案?
考虑建立一个边有上下界的网络流模型。以往所建立的网络流模型,每条边仅有上界,下界都是0,即可以不流。我们给一条边定义上界
r
(
u
,
v
)
r(u,v)
r(u,v)和下界
l
(
u
,
v
)
l(u,v)
l(u,v),其流量
f
(
u
,
v
)
f(u,v)
f(u,v)必须满足
l
(
u
,
v
)
≤
f
(
u
,
v
)
≤
r
(
u
,
v
)
l(u,v)\leq f(u,v) \leq r(u,v)
l(u,v)≤f(u,v)≤r(u,v)。
满足每个点流量守恒的限制且每条边的上下界限制的一个流,被称为无源汇上下界可行流。
如果给定源点和汇点,源点必须只出不进,汇点必须只进不出,别的点要满足流量守恒,这样的模型就叫有源汇上下界可行流。
对于此题,我们可以建立一个有源汇上下界可行流模型:
记
s
1
i
s1_i
s1i表示第
i
i
i行所有
A
A
A之和,记
s
2
j
s2_j
s2j表示第
j
j
j列所有
A
A
A之和,待检验的答案为
m
i
d
mid
mid。
建立一个源点 S S S,一个汇点 T T T, n n n个点表示 n n n行, m m m个点表示 m m m列, S S S向表示各行的点连边,容量上下界为 [ s 1 i − m i d , s 1 i + m i d ] [s1_i-mid,s1_i+mid] [s1i−mid,s1i+mid],各行各列各自连边,容量上下界为 [ L , R ] [L,R] [L,R],表示各列的点向 T T T连边,容量上下界为 [ s 2 j − m i d , s 2 j + m i d ] [s2_j-mid,s2_j+mid] [s2j−mid,s2j+mid]。此时,我们只需要求该网络的一个可行流。
关于上下界网络流的解法,这位同学的博客里总结的很好,我也不想献丑了,请去看这位同学的总结吧:
Click here
其中这两段说得很好:
理解方法:
对于原图中的每条弧,我们把c−b称为它的自由流量,意思就是只要它流满了下界,这些流多少都没问题。
既然如此,对于每条弧<u,v>,我们强制给v提供b单位的流量,并且强制从u那里拿走b单位的流量,这一步对应着两条附加弧。
如果这一系列强制操作能完成的话,也就是有一组可行流了。
理解方法:
有源汇相比无源汇的不同就在于,源和汇是不满足流量平衡的,那么连接<t,s>之后,源和汇也满足了流量平衡,就可以直接按照无源汇的方式建模
综合一下我自己的理解,上下界网络流其实就是先忽略下界限制,再通过添加附加弧,判断附加弧是否满流,来看有没有满足下界限制的流,并求出这个可行流。
对于此题,我们已经保证存在可行流,就不用判断附加边是否满流了。
Code
#include <cstdio>
#include <cstring>
const int N = 207, K = N * 10, INF = 0x3f3f3f3f;
int max(int a, int b) { return a > b ? a : b; }
int min(int a, int b) { return a < b ? a : b; }
int n, m, L, R, a[N][N], s1[N], s2[N], b[N][N];
int l, r, mid, ans = -1;
int S, T, ss, tt, tot = 1, st[K], to[K * K], nx[K * K], len[K * K], totflow[K];
void add(int u, int v, int w)
{
to[++tot] = v, nx[tot] = st[u], len[tot] = w, st[u] = tot;
to[++tot] = u, nx[tot] = st[v], len[tot] = 0, st[v] = tot;
}
int head, tail, que[K], dep[K];
int bfs()
{
memset(dep, 0, sizeof(dep));
head = 1, que[tail = 1] = ss, dep[ss] = 1;
while (head <= tail)
{
int u = que[head++];
for (int i = st[u]; i; i = nx[i])
if (!dep[to[i]] && len[i] > 0)
{
dep[to[i]] = dep[u] + 1, que[++tail] = to[i];
if (to[i] == tt) return 1;
}
}
return 0;
}
int dinic(int u, int flow)
{
if (u == tt) return flow;
int rest = flow, tmp;
for (int i = st[u]; i; i = nx[i])
if (dep[to[i]] == dep[u] + 1 && len[i] > 0)
{
if (!rest) return flow;
tmp = dinic(to[i], min(rest, len[i]));
if (!tmp) dep[to[i]] = 0;
rest -= tmp, len[i] -= tmp, len[i ^ 1] += tmp;
}
return flow - rest;
}
int check()
{
int l1 = 0, r1 = 0, l2 = 0, r2 = 0;
for (int i = 1; i <= n; i++)
{
if (max(s1[i] - mid, L * m) > min(s1[i] + mid, R * m)) return 0;
l1 += max(s1[i] - mid, L * m), r1 += min(s1[i] + mid, R * m);
}
for (int i = 1; i <= m; i++)
{
if (max(s2[i] - mid, L * n) > min(s2[i] + mid, R * n)) return 0;
l2 += max(s2[i] - mid, L * n), r2 += min(s2[i] + mid, R * n);
}
if (l1 > r2 || r1 < l2) return 0;
return 1;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) scanf("%d", &a[i][j]), s1[i] += a[i][j], s2[j] += a[i][j];
scanf("%d%d", &L, &R);
l = 0, r = 200000;
while (l <= r)
{
mid = l + r >> 1;
if (check()) r = mid - 1, ans = mid;
else l = mid + 1;
}
S = n + m + 1, T = S + 1, ss = T + 1, tt = ss + 1; //S,T是原网络源汇点,ss,tt是求可行解时的源汇点
for (int i = 1; i <= n; i++) add(S, i, 2 * ans), totflow[S] -= max(0, s1[i] - ans), totflow[i] += max(0, s1[i] - ans); //下界小于0视作0
for (int j = 1; j <= m; j++) add(j + n, T, 2 * ans), totflow[j + n] -= max(0, s2[j] - ans), totflow[T] += max(0, s2[j] - ans);
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) add(i, j + n, R - L), totflow[i] -= L, totflow[j + n] += L, b[i][j] = tot; //记录一下反向边的编号,该边在残余网络上的流量就是对应的解
add(T, S, INF);
for (int i = 1; i <= n + m; i++)
if (totflow[i] < 0) add(i, tt, -totflow[i]);
else add(ss, i, totflow[i]);
while (bfs()) dinic(ss, INF);
printf("%d\n", ans);
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++) printf("%d ", len[b[i][j]] + L);
printf("\n");
}
return 0;
}
上下界最大流和最小流先挖个坑。