[关闭]
@Dmaxiya 2026-09-13T13:12:56.000000Z 字数 3312 阅读 105

Codeforces 2258 B2 题解(临时文档)

Codeforces


题目大意

给定 根胡萝卜,长度分别为 ()。
有一台切割机,每次操作可以同时选择任意多根胡萝卜(已切出的也可以再选),并对每根被选的胡萝卜执行:若其长度 则不变,否则切成 和 两段( 为正整数,每次操作可不同)。

对于每个 ,问恰好使用 次操作后,最多能选出多少根长度完全相同的胡萝卜(所有选出的长度必须一致)。
每个 独立求解,互不影响。


核心观察

1. 固定目标长度 时的最优切割策略

若我们希望最终得到尽量多长度为 的胡萝卜,那么对于每一根原始胡萝卜,最优的切割顺序是从大到小切:

即第一刀先切出 的块(如果原长度足够),第二刀切 ,依此类推。这样能保证在 刀内获得尽可能多的 小块。


2. 单根胡萝卜对固定 的贡献公式

设当前操作次数为 ,目标长度为 。令:

对于某一根长度为 的胡萝卜:

解释:
- 是长度 理论上最多能包含的 的个数。
- 但由于只有 次切割,最多只能切出 个完整的 小块(因为要得到 个整块需要恰好的整除条件,否则缺一刀)。
- 两者取小即为实际贡献。


3. 最优 的取值范围

当 固定时,若目标长度 太大,使得 ,则没有任何胡萝卜能触发“特殊贡献”,且所有 都会很小甚至为 0,答案必然劣于取更小的 。
因此最优解只需要考虑:

若 ,则唯一有用的 是 ,且此时答案就是所有胡萝卜长度之和(因为全部切成 1)。


快速计算方法(困难版的关键优化)

如果直接枚举每个 和每个 ,再对 根胡萝卜求和,复杂度为 ,不可接受。
我们需要利用长度值域 和前缀和来加速。

预处理

则任意区间 内的胡萝卜总数可在 得到:


固定 时,使用分段求和

对于固定的 ,将所有胡萝卜按 的值分组。
令 ,则 相同的胡萝卜长度落在区间:

这些胡萝卜的数量可以直接用前缀和求出:

这一组对总答案的贡献为:

我们将 从 到 全部累加,就得到了常规部分的总贡献。


处理特殊情况

当存在长度为 的胡萝卜时,这些胡萝卜在常规部分被算作 ,贡献为 ,少算了 。
因此需要额外加上:

(仅当 时存在)


总复杂度分析

对于固定的 ,内层循环次数为 。
有效的 范围是 ,且 。

对所有 求和:

当 时,,实际运行效率完全可行,题解中记为 (将对数平方视作小常数)。

预处理 和 需要 ,总复杂度满足题目限制。


正确性证明概要

  1. 切割顺序最优性:
    每次切出当前最大可获得的 块,保证在 次内最大化 小块数量,这是贪心最优(可通过归纳证明)。

  2. 贡献公式的正确性:
    对任意长度 ,它至多包含 个 ,且 次切割最多产生 个完整小块(除非正好整除产生 个)。公式精确刻画了这两种情况。

  3. 枚举范围的正确性:
    若 ,则 ,没有任何胡萝卜能贡献 ,且所有 或很小,答案不会超过取 的情况,因此可忽略。

  4. 分段求和正确性:
    将所有胡萝卜按 分组,同组贡献相同,利用前缀和统计数量,结果等价于逐项累加。


注意事项

2026 网络赛第二场 K. K-MEX AC 代码

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. int main() {
  4. #ifdef ExRoc
  5. freopen("test.txt", "r", stdin);
  6. #endif // ExRoc
  7. ios::sync_with_stdio(false);
  8. cin.tie(nullptr);
  9. int T;
  10. cin >> T;
  11. while (T--) {
  12. int n;
  13. cin >> n;
  14. unordered_map<int, int> cnt;
  15. cnt.reserve(n * 1.3);
  16. for (int i = 0; i < n; ++i) {
  17. int a;
  18. cin >> a;
  19. ++cnt[a];
  20. }
  21. int base = 0;
  22. vector<int> tmp;
  23. for (int i = 0; i <= n; ++i) {
  24. if (cnt[i] != 0) {
  25. tmp.push_back(i);
  26. --cnt[i];
  27. } else {
  28. base = i;
  29. break;
  30. }
  31. }
  32. for (int x : tmp) {
  33. ++cnt[x];
  34. }
  35. for (int i = 0; i < base; ++i) {
  36. --cnt[i];
  37. }
  38. map<int, int> ansMap;
  39. int q;
  40. cin >> q;
  41. int ans = 0;
  42. while (q--) {
  43. // cout << "q = " << q << '\n';
  44. int k;
  45. cin >> k;
  46. if (ansMap.find(k) != ansMap.end()) {
  47. ans ^= ansMap[k];
  48. // cout << "q = " << q << " ans = " << ansMap[k] << '\n';
  49. continue;
  50. }
  51. vector<int> tmp;
  52. for (int i = base; i <= n; ++i) {
  53. if (cnt[i] != 0) {
  54. tmp.push_back(i);
  55. --cnt[i];
  56. } else if (cnt[k - i] != 0) {
  57. tmp.push_back(k - i);
  58. --cnt[k - i];
  59. } else {
  60. ans ^= i;
  61. ansMap[k] = i;
  62. // cout << "q = " << q << " ans = " << i << '\n';
  63. break;
  64. }
  65. }
  66. for (int x : tmp) {
  67. ++cnt[x];
  68. }
  69. }
  70. cout << ans << '\n';
  71. }
  72. return 0;
  73. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注