あいさか たいがblogAisaka_Taiga的博客
//https://img2018.cnblogs.com/blog/1646268/201908/1646268-20190806114008215-138720377.jpg

快速求popcount的和

Toretto·2023-03-11 19:16·291 次阅读

快速求popcount的和

前置知识#

popcount(n) 表示将 n 转为二进制后的数中 1 的个数。

结论#

i=1n popcount(i)=i=1log2n1[(n>>(i1))&1==1]×(i×2i1+2i×popcount(n>>i)

其中 [(n>>(i1))&1==1] 表示 n 转成二进制以后第 i 位是不是 0

原理#

首先我们需要知道这个东西:

Copy
__builtin_popcount(x)

可恶怎么又是 STL
他的作用就是求出 xpopcount 值,这个东西好像很快我们先把他当作 O(1) 的。

接下来我们考虑用 O(1) 的时间来求得

i=02k1popcount(i)

的做法。

这里以 [0,251] 为例。

先把所有的数都给列出来。

image

然后我们可以看到最低位的规律。

image

依次向后走。

image

image

image

image

我们可以看到每一位里面都是一半是 0,一半是 1

因此我们可以得到下面的公式:

i=02k1popcount(i)=k×2k1

下面以 (11010110)2=(214)10 为例。

其第一位为 1,所以我们直接计算 (00000000)2~(01111111)2popcount 和,也就是 0×27+7×26

其第二位为 1,所以我们直接计算 (10000000)2~(10111111)2popcount 和,也就是 1×26+6×25

其第三位是 0,对答案没有贡献。

其第四位为 1,所以我们直接计算 (11000000)2~(11001111)2popcount 和,也就是 2×24+4×23

其第五位是 0,对答案没有贡献。

其第六位为 1,所以我们直接计算 (11010000)2~(11010011)2popcount 和,也就是 3×22+2×21

其第七位为 1,所以我们直接计算 (11010100)2~(11010101)2popcount 和,也就是 4×21+1×20

其第八位是 0,对答案没有贡献。

但其实我们只需要处理 [0,n) 这个区间分段即可。

最后再加上 popcount((11010110)2)=5

最终结果就是:

0×27+7×26+1×26+6×25+2×24+4×23+3×22+2×21+4×21+1×20+popcount((11010110)2)

=448+256+64+16+9+5

=798

因为 popcount(0)=0,所以统计不统计都可以。

代码#

Copy
scanf("%d", &n); long long tot = 0; int cnt = 0; int x = n; while(x) { if(x & 1) tot += (cnt * (1 << (cnt - 1))) + (1 << cnt) * __builtin_popcount(x >> 1); x >>= 1; cnt++; } tot += __builtin_popcount(n); printf("%lld ", tot);

转载自:https://kaiserwilheim.github.io/OI/fast-popcnt-sum/

虽然是转载但是 LATEX 都是我自己打的QAQ

posted @   北烛青澜  阅读(291)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示
目录