ABC283Ex Popcount Sum 题解
Problem
记 \(\text{popcount}(n)\) 表示 \(n\) 在二进制表示中 \(1\) 的数量。
给定 \(n, m, r\),请求出 \(\sum\limits_{i = 1}^{n} [i\bmod m = r]\times\text{popcount}(i)\)。
多组数据,\(1\le T \le 10^5,\ 1\le m \le n \le 10^9,\ 0\le r < m\)。
时空限制:4s, 1GB。
Sol
发现这个 \(\text{popcount}\) 和取模没有什么关系,不好直接处理,容易想到拆位处理:\(\sum\limits_{i\bmod m = r}^{n}\sum\limits_{j = 0}^{30} [\frac{i}{2 ^ j}\bmod 2 \equiv 1]\)。然后把在求和符号中的判断变一下,即用 \(km + r\) 表示 \(i\),并交换 \(i, j\) 两维:\(\sum\limits_{j = 0}^{30}\sum\limits_{i = 0}^{\lfloor\frac{n - r}{m}\rfloor}[\lfloor\frac{im + r}{2 ^ j}\rfloor\bmod 2 \equiv 1]\)。
接着就是一个很正常的 trick 了,将取模运算用除法表示,变为:\(\sum\limits_{j = 0}^{30}\sum\limits_{i = 0}^{\lfloor\frac{n - r}{m}\rfloor}[\lfloor\frac{im + r}{2 ^ j}\rfloor - 2\times\lfloor\frac{\lfloor\frac{im + r}{2 ^ j}\rfloor}{2}\rfloor = 1]\),然后 \(\lfloor\frac{im + r}{2 ^ j}\rfloor\) 肯定是 \(\ge 2\times\lfloor\frac{\lfloor\frac{im + r}{2 ^ j}\rfloor}{2}\rfloor\) 的,于是可以直接变为相减。然后再根据向下整除的性质可以发现 \(\lfloor\frac{\lfloor\frac{im + r}{2 ^ j}\rfloor}{2}\rfloor\) 的下面部分可以直接相乘。原式就可以化为:\(\sum\limits_{j = 0}^{30}\sum\limits_{i = 0}^{\lfloor\frac{n - r}{m}\rfloor}\lfloor\frac{im + r}{2 ^ j}\rfloor - 2\times\lfloor\frac{im + r}{2 ^ {j + 1}}\rfloor\),这就很简单了,前后分别类欧计算即可。
Code
#include<bits/stdc++.h>
#define ll long long
#define sz(a) ((int) (a).size())
#define vi vector < int >
#define pb emplace_back
using namespace std;
ll F(ll a, ll b, ll c, ll n) {
if(!a)
return b / c * (n + 1);
if(a >= c || b >= c)
return n * (n + 1) / 2 * (a / c) + (b / c) * (n + 1) + F(a % c, b % c, c, n);
ll m = (a * n + b) / c;
return m * n - F(c, c - b - 1, a, m - 1);
}
int n, m, r;
void solve() {
cin >> n >> m >> r;
ll ans = 0;
int lim = (n - r) / m;
for(int i = 0; i <= 30; ++i)
ans += F(m, r, 1 << i, lim) - 2 * F(m, r, 1ll << (i + 1), lim);
cout << ans << "\n";
}
int main() {
ios :: sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T;
cin >> T;
while(T--)
solve();
return 0;
}