Maximum And Queries (hard version)

原题链接

效率异常低下.

\(\tt{Solution}\)

先考虑一下 easy version.

观察到 \(nq \le 10^5\), 所以对于每一次询问, 我们可以按位来进行贪心.

\(2^{59}\) 一直遍历到 \(2^0\), 考虑当前位 \(2^i\) 是否能够出现.

我们可以直接暴力模拟所有 \(n\) 个数的第 \(i\) 位全部变成 \(1\) 的代价之和, 并与目前能够操作次数进行比较即可.

具体地, 我们进行分类讨论:

  1. 如果当前位已经是 \(1\), 则代价为 \(0\).
  2. 如果当前位不是 \(1\), 并且 \(a_j < 2^i\), 则 \(a_j = 2^i\), 代价为 \(2^i - a_j\).
  3. 否则, 将第 \(i\) 位变成 \(1\), 更低位全部变成 \(0\) 即可.

再来看 hard version.

因为 \(1 \le n, q \le 10^6\), 每次询问 \(\mathcal{O}(n)\) 暴力模拟肯定就行不通了.

延续 easy version 的贪心, 需要优化的是 \(\mathcal{O}(n)\) 枚举每个数的过程,

注意到一个性质: 如果一个数更新到第 \(i\) 位是 \(1\) 后, 那么第 \(0 \sim i - 1\) 位都会是 \(0\). 换句话说, 再往后遍历的时候, 如果你想使得答案有第 \(j\) 位, 那么该数一定会贡献 \(2^j\)​ 次操作.

由此可以想到根据答案的最高位 \(2^w\) 进行分讨, 如果 \(w \ge 20\), 那么答案就直接是 \(2^w + \lfloor \frac{left}{n} \rfloor\), 其中 \(left\) 为剩余操作次数.

如果 \(w < 20\), 那么对于每种情况, 我们分别考虑代价, 设答案为 \(ans\).

  1. 更高位已经被更新过, 即 \(a_j\ \& \ ans < ans\), 代价为 \(2^i\), 数量在贪心的过程中顺便统计即可.
  2. 之前未被更新过, 即 \(ans\) 是它高位的子集, 假设这类数共有 \(f_i\) 个, 总和为 \(g_i\), 那么代价即为 \(2^i \times f_i - g_i\).

如何求出 \(f, g\) 数组? 发现符合要求的数满足「某一位必须不存在, 在这一位之前的若干位必须存在」, 预处理时可以枚举位 \(i\), 对于 \(i\) 左侧等价于限制了一段前缀, 并且限制为一个超集状物, 于是对于每一段二进制前缀做一个高维后缀和即可.

时间复杂度 \(\mathcal{O}(n \log^2 V)\).

#include "iostream"

using namespace std;

constexpr int N = 1e6 + 10, M = 20;

#define int long long

int n, q, a[N], mx = 0;
int g[M][1 << M], f[M][1 << M];

void init() {
	cin >> n >> q;
	for (int i = 1; i <= n; ++i)
		cin >> a[i], mx += (1 << 20) - a[i];
	for (int i = 19; ~i; --i) {
		for (int j = 1; j <= n; ++j)
			if (!(a[j] >> i & 1))
				++f[i][a[j]], g[i][a[j]] += a[j] & ((1 << i) - 1);
		for (int j = 0; j <= 19; ++j)
			for (int k = (1 << 20) - 1; k >= (1 << j); --k) if (k >> j & 1)
				f[i][k ^ (1 << j)] += f[i][k], g[i][k ^ (1 << j)] += g[i][k];
	}
}

void calculate() {
	while (q--) {
		int k, ans = 0;
		cin >> k;
		if (k >= mx) {
			cout << (1 << 20) + (k - mx) / n << '\n';
			continue;
		}
		for (int i = 19, c = 0; ~i; --i) {
			int cst = c * (1 << i) - g[i][ans] + (1 << i) * f[i][ans];
			if (k >= cst)
				k -= cst, c += f[i][ans], ans |= 1 << i;
		}
		cout << ans << '\n';
	}
}

void solve() {
	init();
	calculate();
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr), cout.tie(nullptr);
	solve();
	return 0;
} 
posted @   Steven1013  阅读(7)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示