@Dmaxiya
2026-09-13T13:12:56.000000Z
字数 3312
阅读 69
Codeforces
给定 根胡萝卜,长度分别为 ()。
有一台切割机,每次操作可以同时选择任意多根胡萝卜(已切出的也可以再选),并对每根被选的胡萝卜执行:若其长度 则不变,否则切成 和 两段( 为正整数,每次操作可不同)。
对于每个 ,问恰好使用 次操作后,最多能选出多少根长度完全相同的胡萝卜(所有选出的长度必须一致)。
每个 独立求解,互不影响。
若我们希望最终得到尽量多长度为 的胡萝卜,那么对于每一根原始胡萝卜,最优的切割顺序是从大到小切:
即第一刀先切出 的块(如果原长度足够),第二刀切 ,依此类推。这样能保证在 刀内获得尽可能多的 小块。
设当前操作次数为 ,目标长度为 。令:
对于某一根长度为 的胡萝卜:
解释:
- 是长度 理论上最多能包含的 的个数。
- 但由于只有 次切割,最多只能切出 个完整的 小块(因为要得到 个整块需要恰好的整除条件,否则缺一刀)。
- 两者取小即为实际贡献。
当 固定时,若目标长度 太大,使得 ,则没有任何胡萝卜能触发“特殊贡献”,且所有 都会很小甚至为 0,答案必然劣于取更小的 。
因此最优解只需要考虑:
若 ,则唯一有用的 是 ,且此时答案就是所有胡萝卜长度之和(因为全部切成 1)。
如果直接枚举每个 和每个 ,再对 根胡萝卜求和,复杂度为 ,不可接受。
我们需要利用长度值域 和前缀和来加速。
cnt[1..m],其中 cnt[v] 表示长度为 的胡萝卜的数量。则任意区间 内的胡萝卜总数可在 得到:
对于固定的 ,将所有胡萝卜按 的值分组。
令 ,则 相同的胡萝卜长度落在区间:
这些胡萝卜的数量可以直接用前缀和求出:
这一组对总答案的贡献为:
我们将 从 到 全部累加,就得到了常规部分的总贡献。
当存在长度为 的胡萝卜时,这些胡萝卜在常规部分被算作 ,贡献为 ,少算了 。
因此需要额外加上:
(仅当 时存在)
对于固定的 ,内层循环次数为 。
有效的 范围是 ,且 。
对所有 求和:
当 时,,实际运行效率完全可行,题解中记为 (将对数平方视作小常数)。
预处理 和 需要 ,总复杂度满足题目限制。
切割顺序最优性:
每次切出当前最大可获得的 块,保证在 次内最大化 小块数量,这是贪心最优(可通过归纳证明)。
贡献公式的正确性:
对任意长度 ,它至多包含 个 ,且 次切割最多产生 个完整小块(除非正好整除产生 个)。公式精确刻画了这两种情况。
枚举范围的正确性:
若 ,则 ,没有任何胡萝卜能贡献 ,且所有 或很小,答案不会超过取 的情况,因此可忽略。
分段求和正确性:
将所有胡萝卜按 分组,同组贡献相同,利用前缀和统计数量,结果等价于逐项累加。
#include <bits/stdc++.h>using namespace std;int main() {#ifdef ExRocfreopen("test.txt", "r", stdin);#endif // ExRocios::sync_with_stdio(false);cin.tie(nullptr);int T;cin >> T;while (T--) {int n;cin >> n;unordered_map<int, int> cnt;cnt.reserve(n * 1.3);for (int i = 0; i < n; ++i) {int a;cin >> a;++cnt[a];}int base = 0;vector<int> tmp;for (int i = 0; i <= n; ++i) {if (cnt[i] != 0) {tmp.push_back(i);--cnt[i];} else {base = i;break;}}for (int x : tmp) {++cnt[x];}for (int i = 0; i < base; ++i) {--cnt[i];}map<int, int> ansMap;int q;cin >> q;int ans = 0;while (q--) {// cout << "q = " << q << '\n';int k;cin >> k;if (ansMap.find(k) != ansMap.end()) {ans ^= ansMap[k];// cout << "q = " << q << " ans = " << ansMap[k] << '\n';continue;}vector<int> tmp;for (int i = base; i <= n; ++i) {if (cnt[i] != 0) {tmp.push_back(i);--cnt[i];} else if (cnt[k - i] != 0) {tmp.push_back(k - i);--cnt[k - i];} else {ans ^= i;ansMap[k] = i;// cout << "q = " << q << " ans = " << i << '\n';break;}}for (int x : tmp) {++cnt[x];}}cout << ans << '\n';}return 0;}