CF1861F Four Suits
题意
有 \(n\) 个人,\(4\) 种牌。
第 \(i\) 个人有第 \(j\) 种牌 \(a_{i, j}\) 张。
你有第 \(j\) 种牌 \(b_j\) 张。
你需要把你手上的所有牌分给所有人,使得所有人牌的数量相同。
设一种分牌的方案的优美度为所有人最多的种类的牌数 \(x\),以及次多的牌数 \(y\),优美度即 \(x - y\)。
你需要输出每一个人作为 \(x\) 时的最大优美度。
\(n \le 5 \times 10 ^ 4\),保证一定有解。
Sol
考虑枚举当前的人与种类。
\(m\) 表示每个人最终的牌数,\(lim_i\) 表示第 \(i\) 个人距离最终的牌数的差值。
一个显然的结论,若我们已经确定了最多的牌堆,显然该堆增加的牌数为 \(min(b_j, lim_i - a_{i, j})\)。
证明很简单,设当前最多的牌堆为 \(P\),若我们把放到其他牌堆的 \(j\) 种牌与 \(j'\) 种牌交换,最终答案一定不劣。
很容易想到二分一个次大值的上限 \(k\)。
考虑一个 \(\text{flow}\)。
- \(S \to {B_1, B_2, B_3, B_4}\),容量为 \(b_j\)。
- \(B_j \to a_i\),容量为 \(k - a_{i, j}\)。
- \(a_i \to T\),容量为 \(lim_i\)。
套路的,考虑转最小割。
连源点的边只有 \(4\) 条,直接 \(2 ^ 4\) 暴枚。
设当前选择不选择的边集为 \(s\)。
则每个人 \(i\) 的贡献为 \(min(|S| \times k - \sum_{j \in s} a_j, lim_i)\)。
后面这个 \(lim_i\) 是不变的,前面只和 \(k\) 有关。
所以我们可以找到一个分界点,用两个数组直接差分预处理出来。
存一下后面这一坨和 \(k\) 的个数就行了。
每次 \(\text{check}\) 的时候,把当前的割算出来,减去当前选中的点的贡献即可。
由于是求最小割,保留判断最小值是否 \(\ge\) 最大流即可。
Code
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <array>
#define int long long
using namespace std;
#ifdef ONLINE_JUDGE
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
char buf[1 << 23], *p1 = buf, *p2 = buf, ubuf[1 << 23], *u = ubuf;
#endif
int read() {
int p = 0, flg = 1;
char c = getchar();
while (c < '0' || c > '9') {
if (c == '-') flg = -1;
c = getchar();
}
while (c >= '0' && c <= '9') {
p = p * 10 + c - '0';
c = getchar();
}
return p * flg;
}
void write(int x) {
if (x < 0) {
x = -x;
putchar('-');
}
if (x > 9) {
write(x / 10);
}
putchar(x % 10 + '0');
}
bool _stmer;
const int N = 5e4 + 5, M = 4e6 + 5, V = 4e6, inf = 4e18;
array <array <int, 5>, N> s;
array <int, 5> arc;
array <int, N> lim, isl;
array <array <signed, M>, 16> bk, ck;
bool check(int x, int i, int mflow) {
int mcut = inf;
for (int T = 0; T < 16; T++) {
int len = 0, tp0 = 0, res = 0;
for (int k = 0; k < 4; k++) {
if (!(T & (1 << k))) res += arc[k + 1];
else len++, tp0 += s[i][k + 1];
}
res += bk[T][x] + 1ll * ck[T][x] * x * len - min(lim[i], len * x - tp0);
/* cerr << ck[T][x] << endl; */
mcut = min(mcut, res);
}
/* cerr << mcut << endl; */
return mcut >= mflow - lim[i];
}
bool _edmer;
signed main() {
cerr << (&_stmer - &_edmer) / 1024.0 / 1024.0 << "MB\n";
int n = read(), m = 0;
int tp0 = 0, tp1 = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= 4; j++)
s[i][j] = read(), m += s[i][j], isl[i] = max(s[i][j], isl[i]);
if (isl[i] > tp0) tp1 = tp0, tp0 = isl[i];
else tp1 = max(tp1, isl[i]);
}
for (int i = 1; i <= 4; i++)
arc[i] = read(), m += arc[i];
m /= n;
int mflow = 0;
for (int i = 1; i <= n; i++) {
lim[i] = m;
for (int j = 1; j <= 4; j++)
lim[i] -= s[i][j];
mflow += lim[i];
}
for (int T = 1; T < 16; T++) {
int len = 0;
for (int i = 0; i < 4; i++) len += (bool)(T & (1 << i));
for (int i = 1; i <= n; i++) {
int res = 0;
for (int j = 0; j < 4; j++)
if (T & (1 << j))
res += s[i][j + 1];
int pos = (lim[i] + res) / len;
bk[T][0] -= res, bk[T][pos + 1] += res;
bk[T][pos + 1] += lim[i], ck[T][0]++, ck[T][pos + 1]--;
}
for (int i = 1; i <= V; i++)
bk[T][i] += bk[T][i - 1], ck[T][i] += ck[T][i - 1];
}
for (int i = 1; i <= n; i++) {
int tpl = isl[i] == tp0 ? tp1 : tp0, _ans = 0;
for (int j = 1; j <= 4; j++) {
int tp2 = min(arc[j], lim[i]);
arc[j] -= tp2;
int l = tpl, r = s[i][j] + tp2;
int ans = -1;
/* cerr << check(1, i, mflow) << endl; */
/* exit(0); */
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid, i, mflow)) ans = mid, r = mid - 1;
else l = mid + 1;
}
if (~ans) _ans = max(_ans, s[i][j] + tp2 - ans);
arc[j] += tp2;
}
write(_ans), putchar(32);
}
puts("");
return 0;
}