Codeforces 1188D Make Equal

发布时间 2023-06-10 11:16:56作者: lhzawa

设最终所有数变为的值为 \(u\)\(\operatorname{bitcount}(x)\)\(x\) 二进制上为 \(1\) 的位数,由题可得答案即为 \(\sum\limits_{i = 1}^n \operatorname{bitcount}(u - a_i)\)
此时让 \(a_i\) 从小到大排序,答案即为 \(\sum\limits_{i = 1}^n \operatorname{bitcount}(u - a_n + (a_n - a_i))\)
\(v = u - a_n, a_i = a_n - a_i\),答案即为 \(\sum\limits_{i = 1}^n \operatorname{bitcount}(v + a_i)\)

然后可以想到类似于数位 DP 枚举 \(v\) 二进制每位的数字一步一步往后推得到答案。
考虑每一步需要什么条件:
\(i\) 位,枚举得来;为 \(0\)\(1\),同理;\(a_j\)\(i\) 为是否为 \(1\),能 \(O(1)\) 求出;\(a_j\)\(i - 1\) 位有没有进位,这个好像有点难解决,似乎只能 \(O(2^n)\)
其实有一个比较贪心的想法,就是 \(a_j\)\(i - 1\) 位(即 \(a_j\bmod 2^i\))越大就越有可能进位,所以只需要在处理第 \(i\) 位时按 \(a_j\bmod 2^i\) 从大至小排序,这样就只用枚举前缀了,优化到了 \(O(n)\)

\(f_{i, j}\) 为第 \(i\) 位产生了 \(j\) 个进位的答案最小值,现在考虑由 \(f_{i}\) 推到 \(f_{i + 1}\)
\(a_{1}\sim a_{n}\)\(i\) 位一共有 \(x\)\(1\)\(a_1\sim a_{j}\)\(i\) 位一共有 \(y\)\(1\),于是可以得到:

  1. \(i\) 位为 \(1\) 且进位,个数为 \(y\)
  2. \(i\) 位为 \(0\) 且进位,个数为 \(j - y\)
  3. \(i\) 位为 \(1\) 且不进位,个数为 \(x - y\)
  4. \(i\) 位为 \(0\) 且不进位,个数为 \(n - j - (x - y)\)

考虑 \(v\)\(i\) 位不同取值会有什么贡献:

  1. \(0\),则 1 情况会进位,23 情况会增加 \(1\) 的贡献,\(f_{i, j} + (j - y) + (x - y)\rightarrow f_{i + 1, y}\)
  2. \(1\),则 123 情况会进位,14 情况会增加 \(1\) 的贡献,\(f_{i, j} + y + (n - j - (x - y))\rightarrow f_{i + 1, y +(j - y) + (x - y)}\)

时间复杂度 \(O(n\log_2 n\log_2 \max\{a_i\})\)

// lhzawa(https://www.cnblogs.com/lhzawa/)
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = 60;
long long a[N];
int f[M + 2][N];
int main() {
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%lld", &a[i]);
	}
	sort(a + 1, a + n + 1);
	for (int i = 1; i <= n; i++) {
		a[i] = a[n] - a[i];
	}
	// for (int i = 1; i <= n; i++) {
	// 	printf("%lld ", a[i]);
	// }
	// printf("\n");
	memset(f, 0x3f, sizeof(f));
	f[0][0] = 0;
	for (int i = 0; i < M; i++) {
		sort(a + 1, a + n + 1, [&i](long long x, long long y) {
			return (x & ((1ll << i) - 1)) > (y & ((1ll << i) - 1)); 
		});
		int x = 0;
		for (int j = 1; j <= n; j++) {
			x += ((a[j] >> i) & 1);
		}
		int y = 0;
		for (int j = 0; j <= n; j++) {
			// printf("%d ", f[i][j]);
			y += ((a[j] >> i) & 1);
			function<void (int&, int)> Min = [](int& a, int b) -> void {
				a = min(a, b);
				return ;
			};
			Min(f[i + 1][(y) + (j - y) + (x - y)], f[i][j] + y + (n - j - (x - y)));
			Min(f[i + 1][y], f[i][j] + (j - y) + (x - y));
		}
		// printf("\n");
	}
	printf("%d\n", f[M][0]);
	return 0;
}