CF1861F Four Suits【网络流,贪心】

发布时间 2023-11-06 16:28:12作者: came11ia

\(n\) 个人,\(4\) 种不同的卡牌,初始第 \(i\) 个人有 \(a_{i,j}\) 张第 \(j\) 种卡牌。

你是局外人,手里第 \(j\) 种卡牌有 \(b_j\) 个,你现在要把你的卡牌分给这 \(n\) 个人,使得分完之后每个人手里的卡牌总数相等,保证有解。

\(i\) 个人最终的分数是手里每种卡牌的数量的最大值。分数最高的那个人如果分数为 \(x\),除了他之外分数最高的人的分数为 \(y\),那么他会获得 \(x-y\) 的喜悦程度(如果存在两个分数最高的,那么他们的喜悦度都是 \(0\)),其他人会获得 \(0\) 的喜悦程度。

对于每个人,算出他最大的可能的喜悦程度。

\(2 \le n \le 5 \times 10^4\)\(0 \le b_j,a_{i,j} \le 10^6\)


\(M\) 为最终每个人的总卡牌数,\(c_i = M - \sum\limits_j a_{i,j}\) 表示第 \(i\) 个人需要收到多少张牌。

考虑怎么计算一个人 \(i\) 的答案,假设我们希望第 \(i\) 个人最多的卡牌为类型 \(j\),那么通过调整可以证明,最优策略一定是尽可能将类型 \(j\) 的卡牌给 \(i\)。显然我们最多能给 \(\min(b_j,c_i)\) 个卡牌 \(j\) 给第 \(i\) 个人。这样第 \(i\) 个人每种卡牌个数的最大值就确定了,我们的目标变为让另外 \(n-1\) 个人每种卡牌个数的最大值尽可能小。考虑二分答案 \(mid\),用网络流模型检验 \(mid\) 是否可行:

新建源点 \(S\) 和汇点 \(T\),以及 \(x_1 \sim x_4\)\(y_1 \sim y_n\),连边:

  • \(S \to x_i\),容量 \(b_i\)
  • \(x_i \to y_j(j \neq i)\),容量 \(mid - a_{j,k}\)
  • \(y_j \to T(j \neq i)\),容量 \(c_j\)

最后检验最大流是否为 \(\sum\limits_{i\ne j}c_j\) 即可。

然而每次检验都建一次图是没法接受的。考虑最大流最小割定理,只需要判断是否所有割的权值都 \(\geq \sum\limits_{i\ne j}c_j\) 即可。我们枚举哪些与源点相连的边没被割掉,那么割掉剩余的边的代价就是 \(\sum\limits_{i \neq j}\min(c_j,\sum\limits_{x \in S} mid - a_{j,x})\)。割掉与 \(S\) 相连的边的代价是 \(\sum\limits_{x\notin S}b_x\)。后者容易计算,前者可以对于每个 \(S\) 差分预处理出每个 \(mid\) 对应的值,就可以快速检验了。总时间复杂度 \(\mathcal{O}(n \cdot 4^2 \cdot 2^4 \log V + 2^4 \cdot V)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
constexpr int N = 5e4 + 5, K = 4e6 + 5;
bool Mbe;
int n, M, mx1, mx2;
int a[N][4], b[4], mx[N], c[N], val[1 << 4][N], cnt[1 << 4][K];
LL sumc, sum[1 << 4][K];
bool Med;
int main() {
//	fprintf(stderr, "%.9lf\n", 1.0 * (&Mbe - &Med) / 1048576.0);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> n;
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j < 4; j++) cin >> a[i][j], M += a[i][j], mx[i] = max(mx[i], a[i][j]);
		mx2 = max(mx2, min(mx1, mx[i]));
		mx1 = max(mx1, mx[i]);
	}
	for (int i = 0; i < 4; i++) cin >> b[i], M += b[i];
	M /= n;
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j < 4; j++) c[i] += a[i][j];
		c[i] = M - c[i], sumc += c[i];
		for (int s = 0; s < 1 << 4; s++)
			for (int j = 0; j < 4; j++) {
				if (s & (1 << j)) val[s][i] += a[i][j];
			}
	}
	for (int s = 1; s < 1 << 4; s++) {
		for (int i = 1; i <= n; i++) {
			int x = (val[s][i] + c[i]) / __builtin_popcount(s) + 1;
			sum[s][0] -= val[s][i], cnt[s][0]++;
			sum[s][x] += val[s][i], cnt[s][x]--;
			sum[s][x] += c[i];
		}
		for (int i = 1; i <= M; i++) sum[s][i] += sum[s][i - 1], cnt[s][i] += cnt[s][i - 1];
	}
	int ans;
	for (int i = 1; i <= n; i++) {
		ans = 0;
		int lim = mx[i] == mx1 ? mx2 : mx1;
		for (int j = 0; j < 4; j++) {
			int cur = min(c[i], b[j]);
			int L = lim, R = M, res = M;
			b[j] -= cur;
			auto check = [&](int m) {
				for (int s = 0; s < 1 << 4; s++) {
					LL cut = sum[s][m] + 1LL * cnt[s][m] * __builtin_popcount(s) * m;
					cut -= min(c[i], __builtin_popcount(s) * m - val[s][i]);
					for (int k = 0; k < 4; k++) if (!(s & (1 << k))) cut += b[k];
					if (cut < sumc - c[i]) return false;
				}
				return true;
			};
			while (L <= R) {
				int m = L + R >> 1;
				if (check(m)) R = m - 1, res = m;
				else L = m + 1;
			}
			ans = max(ans, a[i][j] + cur - res), b[j] += cur;
		}
		cout << ans << " ";
	}
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << "ms\n";
	return 0; 
}