0923考试T3 二进制,位运算
0923考试T3
题目描述:
前方的地上散落着B朵樱花,此时刮起了风,便引来一场樱花雨。樱花雨一共持续了N秒。每一秒都会有A朵樱花飘落。小Q细心的记录了每一秒时间后地上樱花的数目,并将其转换成了二进制。小Q想请你统计一下这些二进制数一共有多少个1。
\(2 \le N \le 10 ^ 9, 1 \le A \le 10 ^ 4, 1 \le B \le 10 ^ {16}\)
二进制,位运算。
对于一个等差数列\(b + a, b +2 * a, b + 3 * a,...,b+n * a\), 第\(i\)项与第\(2 ^ k + i\)项的二进制下第\(k\)位是相同的。为啥呢?因为任何一个二进制,两个它相加相当于乘2,相当于左移一位,那那么第一位就为0了(假设没有第0位,从第一位开始编号),所以现在\(k = 1\),\(b\)与\(b + 2 ^ 1 * a\)第\(1\)位相同。
先介绍一下\(calc\)函数,\(calc(x, t)\)代表对于前\(x\)项,\(2 ^ k = t\),第\(k - 1\)位上有几个1。
假设当前\(k = 3\),如果说第\(i\)项的后\(k - 1\)位小于100,对应这句:\((b + a * i) \% t < (t >> 1)\),那么这一项的第\(k - 1\)为肯定不为1。从\((b + a * i) \% t\)到\((t >> 1)\)中间的数肯定第\(k - 1\)位也不为0,那么直接跳就好了,跳的项数就是\(((t >> 1) - (b + a * i) \% t - 1) / a\)。下面$else $同理。
主函数里那个循环:
当\(n >= i\)时,\(i\)的含义有两个:前\(i\)项,\(2^k = i\),枚举到第\(k\)位。此时可以用上面那个定理,因为他有循环节,\(b, b + 2^k, b +2^k + 2 ^ k...\),它们的第\(k\)位都相同。此时我们只需要计算\((n / i) * calc(i, i) + calc(n \% i, i)\)就好了。
当\(n >= i\)时,\(i\)的含义只有\(2^k = i\),枚举到第\(k\)位。此时我们只要算\(calc(n, i)\)就好了。
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
long long a, b, n, T, ans;
long long calc(long long x, long long t) {
long long res = 0;
// cout << x << " " << t << endl;
for(long long i = 1;i <= x; i++) {
if((b + a * i) % t < (t >> 1))
i = min(x, i + ((t >> 1) - (b + a * i) % t - 1) / a);
else {
long long tmp = min(x, i + (t - (b + a * i) % t - 1) / a);
res += tmp - i + 1; i = tmp;
}
}
return res;
}
int main() {
freopen("flowers.in","r",stdin); freopen("flowers.out","w",stdout);
T = read();
while(T --> 0) {
ans = 0;
a = read(); b = read(); n = read();
for(long long i = 2;i < ((b + a * n) << 1); i <<= 1) {
if(n / i) ans += 1ll * (n / i) * calc(i, i);
ans += calc(n % i, i);
}
printf("%lld\n", ans);
}
fclose(stdin); fclose(stdout);
return 0;
}
/*
2
7 4 1
5 8 2
2
2 4 5
5 9 5
*/