题解: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)\)。
时间复杂度 \(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\) 代表的数。

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号