题解:CF2118C Make It Beautiful

CF2118C Make It Beautiful

题意

给定序列 \(\{a_n\}\),定义一个序列的价值为每个数字的 \(\operatorname{popcount}\) 之和。

最多进行 \(k\) 次操作,每次操作可以给一个数加一。

\(n\le5\times10^3\)\(a_i\le10^9\)\(k\le10^{18}\)

解法

首先我们先将所有的偶数 \(+1\) 变为奇数。

然后将最后两位为 \(01\) 的加两次,使其变为 \(11\)

然后将最后三位为 \(011\) 的加四次,使其变为 \(111\)

然后将最后四位为 \(0111\) 的加八次,使其变为 \(1111\)


以此类推,我们每次将最后 \(i\) 位为 \(0\underbrace{111\cdots111}_{i\text{ 个 }1}\) 的数加 \(2^i\) 次,如果不能达到次数就不进行修改。

那么我们需要高效地判断最后一个 \(0\) 的位置。这个可以将原数取反后查询 \(\operatorname{lowbit}\) 解决。

从小到大枚举 \(i\) 直到 \(k\) 耗尽为止。

正确性证明

下面证明为什么要从小到大枚举 \(i\)

假设我们有两种选择,第一种 \(+2^i\) 使第 \(i\) 位变为 \(1\);第二种 \(+2^j\) 使第 \(j\) 位变为 \(1\)。其中 \(j>i\)。由于第一种花费更少,留给其它位置操作的可能性更多,所以我们一定会选择第一种。

这样就等价于从小到大枚举 \(i\)

时间复杂度

  • 枚举 \(i\),保险起见枚举 \(0\sim62\)\(O(\log k)\)
    • 枚举每一个数字,\(O(n)\)
      • 修改这个数字,\(O(1)\)

时间复杂度 \(O(n\log k)\),可以通过。

此外,不优化的时间复杂度 \(O(n\log^2k)\),也可以通过。

代码

Submission #325574515 - Codeforces

/**
 * Problem: CF2118C Make It Beautiful
 * Author:  OIer_wst
 * Date:    2025-06-22
 */
#include <bits/stdc++.h>
#define lowbit(i) ((i)&(-(i)))
using lint = long long;
const int N = 5e3 + 10;

int T, n;
lint k, ans, a[N];

int main() {
  for (std::cin >> T; T; --T) {
    std::cin >> n >> k, ans = 0;
    for (int i = 1; i <= n; ++i) std::cin >> a[i];
    for (int i = 0; i < 63; ++i) { // 注意!这里 k 可能很大,可能会使得很多位为 1,所以要枚举到 62,再大一点就会爆 long long
      bool flag = false; // 可以用 flag 加速判断,k 无法使用时直接退出(显然花费在不断增加)
      for (int j = 1; j <= n; ++j) {
        if (lowbit(~a[j]) == 1ll << i) {
          if (k >= 1ll << i) {
            k -= 1ll << i;
            a[j] |= 1ll << i;
          } else {
            flag = true;
            break;
          }
        }
      }
      if (flag) break;
    }
    for (int i = 1; i <= n; ++i)
      ans += __builtin_popcountll(a[i]);
    std::cout << ans << std::endl;
  }
  return 0;
}

模型归纳

本题是一个位贡献贪心问题,常见做法就是拆位考虑。这也是处理二进制问题的常见方法。

技巧

  • lowbit(~x) 可以求出 \(x\) 最低位的 \(0\) 代表的数。
posted @ 2025-06-22 21:11  OIer_wst  阅读(41)  评论(0)    收藏  举报