#P2905. 第2题-数字退化消耗
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 92
            Accepted: 18
            Difficulty: 3
            
          
          
          
                       所属公司 : 
                              美团
                                
            
                        
              时间 :2025年4月26日-技术岗
                              
                      
          
 
- 
                        算法标签>位运算          
 
第2题-数字退化消耗
题解
题面描述
给定正整数 n,定义对每个整数 x 的“退化运算”:
- 
计算
Backx=xand(−x)其中
and表示按位与运算。 - 
若 Backx>0,则令
- 
新的 x←x−Backx;
 - 
本次退化的消耗为
max(0,Backx−1). 
 - 
 - 
重复上述步骤,直到 x=0 为止。
 
我们记数字 x 在整个退化过程中的 总消耗 Costx 为:
- 初始的 x 与每次退化的消耗值,全部按位或得到的结果。
 
即

现在要求计算
i=1∑nCosti.思路
- 
观察 Costx 的结构:
- 
退化过程中,每一步的 Backx 恰好是 x 的最低位的 1 所对应的 2k。
 - 
相应的消耗 max(0,Backx−1)=2k−1(当 k=0 时为 0)。
 - 
将所有这些消耗值按位或,再与初始的 x 按位或,最终得到:

 - 
注意到如果 x 的最高位在位置 k,那么 x 的二进制第 k 位为 1,而所有 2j−1 (j≤k) 的按位或,会使得最终结果的二进制位 0,1,…,k 全部为 1,即:
Costx = 2k+1−1,k = ⌊log2x⌋.
 
 - 
 - 
转化为求和:
 

- 
高效计算:
- 
对每个 k=0,1,2,…,⌊log2n⌋,令区间
Lk = 2k,Rk =min(n,2k+1−1).
 - 
在此区间内,⌊log2i⌋=k 的 i 有
cntk = Rk - Lk + 1
 - 
因此

 - 
最终答案为

 
 - 
 
C++
#include <bits/stdc++.h>
using namespace std;
using int64 = long long;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T;
    cin >> T;  // 读取测试用例个数
    while (T--) {
        int64 n;
        cin >> n;  // 对每个测试读取 n
        int64 sum_pow = 0;
        // 枚举 k 表示 2^k
        for (int k = 0; (1LL << k) <= n; ++k) {
            int64 L = 1LL << k;
            int64 R = min(n, (1LL << (k + 1)) - 1);
            int64 cnt = R - L + 1;  // 区间内 i 的个数
            sum_pow += cnt * L;     // 累加 cnt * 2^k
        }
        // 最终结果为 2 * sum_pow - n
        int64 ans = 2 * sum_pow - n;
        cout << ans;
        if (T) cout << ' ';
    }
    return 0;
}
Python
import sys
# 处理输入
input = sys.stdin.readline
T = int(input())  # 读取测试用例数
for _ in range(T):
    n = int(input())  # 读取 n
    sum_pow = 0
    k = 0
    # 枚举各个二进制位
    while (1 << k) <= n:
        L = 1 << k
        R = min(n, (1 << (k + 1)) - 1)
        cnt = R - L + 1  # 区间 [2^k, 2^(k+1)-1] 内的数目
        sum_pow += cnt * L
        k += 1
    # 按公式计算
    ans = 2 * sum_pow - n
    print(ans, end=' ')
Java
import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int T = sc.nextInt();  // 读取测试用例数量
        while (T-- > 0) {
            long n = sc.nextLong();  // 读取 n
            long sumPow = 0;
            // 枚举 k,使 2^k <= n
            for (int k = 0; (1L << k) <= n; k++) {
                long L = 1L << k;
                long R = Math.min(n, (1L << (k + 1)) - 1);
                long cnt = R - L + 1;  // 区间长度
                sumPow += cnt * L;     // 累加 cnt * 2^k
            }
            // 计算最终答案
            long ans = 2 * sumPow - n;
            System.out.print(ans);
            if (T > 0) System.out.print(" ");
        }
        sc.close();
    }
}
        题目内容
小美有一些运算:她定义一个整数 x 的退化运算 Backx 为自己按位与自己的负数,形式化的说, Backx=(x)and(−x) ;一次成功的退化后,x 变为 x−Backx ;消耗为 max { 0,Backx−1 } 。
退化是可以持续的进行的,例如,当 x=37 时:
- 
第一次退化,x=37,Back37=(37)and(−37)=1,退化为 37−Back37=36 ,消耗 0 ;
 - 
在刚刚的基础上进行第二次退化,x=36,Back36=(36)and(−36)=4,退化为 36−Back36=32 ,消耗 3 ;
 - 
在刚刚的基础上进行第三次退化,x=32 , ......
 - 
......
 
我们记数字 x 退化过程中的总消耗 Costx 为:初始的 x 与每一次退化的消耗 按位或 后得到的结果。
例如 Cost37=37 or 0 or 3 or ⋅⋅⋅ 。
现在,你已经完全掌握小美的运算了,你需要计算的是,1 ~ n 中,全部数字退化总消耗之和,即
∑i=1nCosti 。
输入描述
每个测试文件均包含多组测试数据,第一行输入一个整数 T(1≦T≦2×105) 代表数据组数,每组测试数据描述如下:
在一行上输入一个整数 n(1≦n≦109) 代表运算的上界。
输出描述
对于每一组测试数据,在同一行上连续输出一个整数,代表 1 ~ n 中,全部数字退化总消耗之和。
样例1
输入
5
1
2
3
4
11451
输出
1 4 7 14 981396131
说明
Cost1=1,Cost2=3,Cost3=3,Cost4=7 。