题解 //「BZOJ2406」矩阵
赛时公告
现在呢?:现在有弹窗了吗 「2023-07-19 16:45:07」
此时无声胜有声。
F.「BZOJ2406」矩阵
http://222.180.160.110:1024/contest/3825/problem/7
这是头一次见识到把矩阵和网络流结合在一起的题目。不过这种处理方式也是我们在学习二分图时的常客了:把行和列连边表示某一元素。
我们发现 \(B_{i, j}\) 有给定的上下界,故我们考虑 上下界网络流。那怎么去表示 \(B_{i, j}\) 呢?这就要联系到我们刚刚说过的连边方式:用边 \(i\to j\) 的流量来表示 \(B_{i, j}\),有 \([L, R]\) 的上下界。
可是我们除了 \([L,R]\) 的限制,还有最大值这个条件呀,怎么办呢?
注意到题目要求最大的最小,自然想到二分答案。设答案为 \(x\),则我们需要保证每行每列的答案都 \(\le x\)。每行每列,这刚好是我们的建点方式。这对点本身作出了要求,这套路我们熟,让大源点向行连边、列向大汇点连边就好。
那么这些边的上下界怎么办呢?我们已知 \(|S_A-S_B|\le x\),那么变形得:
照理来说,两行的符号相反,我们现在已经得到了一个具有对称美的上下界:\(S_A-x\le S_B\le S_A+x\),就应该速速连边了,可是我怎么看都觉得不舒坦:这个不等式可是带条件的,就这么直接拿来做上下界真的没问题吗?
答案是没问题,因为我看的题解是这么写的 本着探索求真精神,我们考虑尊重原不等式(因为原不等式的每一行刚好也有两个相反的符号),将这些边拆成两条,一条的上下界是 \([S_A-x, S_A]\),另一条是 \([S_A,S_A+x]\)。6。我明白题解为什么这么写了,一个的下界就是另一个的上界,那直接合并不就行了,这个 naive trick 题解都不屑于写出来。
然后跑个可行流就可以了。注意要保证边的下界为非负。
#define int long long
namespace XSC062 {
using namespace fastIO;
const int lim = 2e5;
const int maxn = 405;
const int inf = 1e18;
const int maxm = 3e5 + 5;
struct _ {
int v, w, n;
_() {}
_(int v1, int w1, int n1) {
v = v1, w = w1, n = n1;
}
};
_ u[maxm];
int gs, gt, tot;
int a[maxn][maxn];
int l, mid, r, res;
int h[maxn], dif[maxn];
int n, m, cnt, s, t, L, R;
int vis[maxn], now[maxn], dep[maxn];
inline int max(int x, int y) {
return x > y ? x : y;
}
inline int min(int x, int y) {
return x < y ? x : y;
}
bool BFS(int n) {
std::fill(vis + 1, vis + n + 1, 0);
std::fill(dep + 1, dep + n + 1, 0);
std::queue<int> q;
dep[gs] = 1, vis[gs] = 1;
q.push(gs), now[gs] = h[gs];
while (!q.empty()) {
int f = q.front();
q.pop();
for (int i = h[f]; i; i = u[i].n) {
int v = u[i].v, w = u[i].w;
if (vis[v] == 1 || w == 0)
continue;
vis[v] = 1, now[v] = h[v];
dep[v] = dep[f] + 1, q.push(v);
if (v == gt)
return 1;
}
}
return 0;
}
int findP(int x, int flow = inf) {
if (x == gt)
return flow;
int rest = flow, i;
for (i = now[x]; rest && i; i = u[i].n) {
now[x] = i;
int v = u[i].v, w = u[i].w;
if (dep[v] != dep[x] + 1 || w == 0)
continue;
int t = findP(v, min(rest, w));
if (t == 0)
dep[v] = 0;
rest -= t;
u[i].w -= t, u[i ^ 1].w += t;
}
return flow - rest;
}
inline int Dinic(int n) {
int res = 0;
while (BFS(n)) {
int t = findP(gs);
while (t) {
res += t;
t = findP(gs);
}
}
return res;
}
inline void add(int x, int y, int w) {
u[++tot] = _(y, w, h[x]);
h[x] = tot;
return;
}
inline void add(int x, int y, int d, int u) {
add(x, y, u - d), add(y, x, 0);
dif[x] -= d, dif[y] += d;
return;
}
inline void Init(void) {
tot = 1, cnt = 0;
memset(h, 0, sizeof (h));
memset(dif, 0, sizeof (dif));
return;
}
bool check(int x) {
Init();
s = n + m + 1, t = s + 1;
add(t, s, inf), add(s, t, 0);
gs = t + 1, gt = t + 2;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j)
add(i, j + n, L, R);
}
for (int i = 1; i <= n; ++i) {
int sum = 0;
for (int j = 1; j <= m; ++j)
sum += a[i][j];
add(s, i, max(0, sum - x), sum + x);
}
for (int j = 1; j <= m; ++j) {
int sum = 0;
for (int i = 1; i <= n; ++i)
sum += a[i][j];
add(j + n, t, max(sum - x, 0), sum + x);
}
for (int i = 1; i <= t; ++i) {
if (dif[i] < 0) {
add(i, gt, -dif[i]);
add(gt, i, 0);
}
else if (dif[i] > 0) {
add(gs, i, dif[i]);
add(i, gs, 0);
cnt += dif[i];
}
}
return (Dinic(gt) == cnt);
}
int main() {
read(n), read(m);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j)
read(a[i][j]);
}
read(L), read(R);
l = 0, r = lim, res = -1029;
while (l <= r) {
mid = (l + r) >> 1;
if (check(mid)) {
res = mid;
r = mid - 1;
}
else l = mid + 1;
}
print(res);
return 0;
}
} // namespace XSC062
#undef int
你最好有要事相求.jpg
—— · EOF · ——
真的什么也不剩啦 😖