1 条题解

  • 0
    @ 2025-8-24 23:13:34

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar qwqerty
    计算机科学教育新生态。

    搬运于2025-08-24 23:13:34,当前版本为作者最后更新于2025-04-16 19:36:27,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    其实这是我真正意义上的第一次不看题解作出的绿题,因为我做题前总会把题解先扫一遍看看难不难。

    解题思路

    先将完整的数统一处理,再处理剩余部分。
    比方说 x=25x=25
    01101110010111011110001000110111001011101111000100
    为方便处理,直接丢掉第一位。再通过位数进行分割。
    $\color{red}1\color{blue}1011\color{green}100101110111\color{purple}1000\color{IAKIOI}100$。
    分析一下具体的分割过程:

    • 割下 11 位数,共有 211=12^{1-1}=1 个数,每个数有 11 位,一共 1×211=11\times2^{1-1}=1 位。还剩 241=2324-1=23 位。
    • 割下 22 位数,共有 221=22^{2-1}=2 个数,每个数有 22 位,一共 2×221=42\times2^{2-1}=4 位。还剩 234=1923-4=19 位。
    • 割下 33 位数,共有 231=42^{3-1}=4 个数,每个数有 33 位,一共 3×231=123\times2^{3-1}=12 位。还剩 1912=719-12=7 位。
    • 割下 44 位数,由于剩下的数小于 4×241=324\times2^{4-1}=32 位,所以无法完全分割。只能割下 74=1\lfloor\frac{7}{4}\rfloor=1 个数,还剩 7mod4=37\bmod 4=3 位。

    这一部分的代码如下:

    x--;
    int i;
    for (i = 0; ; i++) {
        if (x < (i + 1) * (1ll << i)) break;
        x -= (i + 1) * (1ll << i);
        res += (1ll << i);
    }
    res += x / (i + 1);
    x %= (i + 1);
    

    所以一共分割出了 1+2+4+1=81+2+4+1=8 个数。
    我们先处理这 88 个数,再将剩下的 33 个数单独处理。

    现在的任务是求 popcount\operatorname{popcount} 的前缀和。
    部分内容借鉴自该博客,如侵删。
    如果我们把二进制下的 001515 的数全部列出来,找一下规律:

    $$\color{blue}0000000011111111\\ \color{green}0000111100001111\\ \color{purple}0011001100110011\\ \color{red}0101010101010101 $$

    分开观察一下每一行,可以发现每一行 00 的个数和 11 的个数都相等!
    这是因为如果 nn 的二进制下第 kk 位数是 11 的话,那么有 nmod2k2k11\dfrac{n\bmod2^k}{2^{k-1}}\ge1,也就是说,nmod2k2k1n\bmod2^k\ge2^{k-1}。可以发现,它正好是占一半的。
    所以 $\sum\limits_{i=0}^{2^k-1}\operatorname{popcount}(i)=\dfrac{k\times2^k}{2}=k\times2^{k-1}$。
    那么对于一个任意的正整数 nn,如何计算 i=0npopcount(i)\sum\limits_{i=0}^{n}\operatorname{popcount}(i) 呢。考虑拆位计算贡献的方式,下面以 n=22=(10110)2n=22=(10110)_2 为例:

    • 最高位为 11,统计 (00000)2(00000)_2(01111)2(01111)_2。共对答案产生了 4×241=324\times2^{4-1}=32 的贡献。
    • 第三高位为 11,统计 (10000)2(10000)_2(10011)2(10011)_2,共对答案产生了 1×22+2×221=81\times2^2+2\times2^{2-1}=8 的贡献。
    • 第四高位为 11,统计 (10100)2(10100)_2(10101)2(10101)_2,共对答案产生了 2×21+1×211=52\times2^1+1\times2^{1-1}=5 的贡献。

    最后还要统计 popcount(n)\operatorname{popcount}(n),所以答案为 32+8+5+3=4832+8+5+3=48
    为方便处理,可以直接将 nn 增加 11 在进行计算,最后就不用统计 popcount(n)\operatorname{popcount}(n) 了。
    这个部分的代码如下:

    int getsum(int x) {
    	int res = 0, cnt = 0;
    	x++;
    	while (x) {
    	    if(x & 1) res += (cnt * (1ll << (cnt - 1))) + (1ll << cnt) * popcount(x >> 1);
    	    x >>= 1;
    	    cnt++;
    	}
    	return res;
    }
    

    现在我们已经处理完了完整部分,接下来我们处理剩余部分。
    还是分析我们的事例,最后剩下 33 位。可以发现,这个数就是下一个数的前三位。
    我们如何计算 nn 的前 kk 位?首先我们计算 nn 的二进制位数,也就是 log2n\lfloor\log_2n\rfloor。我们可以将后 log2nk+1\lfloor\log_2n\rfloor-k+1 位直接删去。这样留下来的就是前 kk 位了。最后我们对它求一遍 popcount\operatorname{popcount} 即可。

    于是我们就做完了这道题。
    本篇题解共有 37033703 字符(包含这句话)。

    AC 代码

    别问我这是什么语言。

    #include <bits/stdc++.h>
    #define int long long/*
    '''
    */
    using namespace std;
    int x, res;
    int popcount(int x) {
    	int res = 0;
    	while (x) {
    		res += x & 1;
    		x >>= 1;
    	}
    	return res;
    }
    int getsum(int x) {
    	int res = 0, cnt = 0;
    	x++;
    	while (x) {
    	    if(x & 1) res += (cnt * (1ll << (cnt - 1))) + (1ll << cnt) * popcount(x >> 1);
    	    x >>= 1;
    	    cnt++;
    	}
    	return res;
    }
    signed main() {
    	cin >> x; x--;
    	int i;
    	for (i = 0; ; i++) {
    		if (x < (i + 1) * (1ll << i)) break;
    		x -= (i + 1) * (1ll << i);
    		res += (1ll << i);
    	}
    	res += x / (i + 1);
    	x %= (i + 1);
    	cout << getsum(res) + popcount(res + 1 >> (int)log2l(res + 1) - x + 1);
        return 0;
    }
    /*
    '''
    def popcount(x):
        res = 0;
        while x:
            res += x & 1;
            x >>= 1;
        return res;
    def getsum(x):
        res = 0;cnt = 0;
        x += 1;
        while x:
            if x & 1:
                if cnt >= 1:res += cnt * (1 << (cnt - 1)) + (1 << cnt) * popcount(x >> 1);
                else:res += (1 << cnt) * popcount(x >> 1);
            x >>= 1;
            cnt += 1;
        return res;
    x = int(input())
    x -= 1;
    res = 0;i = 0;
    while True:
        if x < (i + 1) * (1 << i):break
        x -= (i + 1) * (1 << i);
        res += (1 << i);
        i += 1;
    res += x // (i + 1);
    x %= (i + 1);
    ans = getsum(res) + popcount((res + 1) >> ((res + 1).bit_length() - x));
    print(ans);
    # */
    
    • 1

    信息

    ID
    12038
    时间
    5000ms
    内存
    512MiB
    难度
    4
    标签
    递交数
    0
    已通过
    0
    上传者