P17729 [SCP-J 2026] 进位 题解
Chen_Johnny · · 题解
P17729 [SCP-J 2026] 进位 题解
看到这道题我们发现这道题是一道策略类问题。
思路
Step 1: 大体框架
这道题显然就是一道分段问题,也就是序列 DP 的模板。
那我们不妨设
转移方程也很显然:
显然,这个复杂度为
- 如何求快速求
C(a,b) ; - 如何优化掉 DP 的
j 循环。
Step 2: 快速求 C(a,b)
首先,题目在末尾特地写了 __builtin_popcount 的用法,显然就是让你用。(还有更显然的吗)
接下来,我们要用一个结论:二进制加法的进位数等于加法前后数中 1 的数量之差。
::::info[证明]
采用一一对应法,即证:每一次进位都会减少一个 1。
分类讨论:
1+1:此时会变为10,减少了 1 个1;1+0:并没有进位,1的数量不变;0+0:并没有进位,1的数量不变。
| 这里很多人可能会问,如果进位了之后因为这次进位有进位了呢?那实际上就是下一次计算涵盖的了。可以自己品味一下。 |
|---|
为了方便,后文将 __builtin_popcount 简称为函数
那么,对于任意两个数
然后,我们注意这个
此时,我们发现
这些都可以用前缀和预处理,并以
Step 3: 优化
对于很多二进制有关的题目而言,按照位数进行处理是一个非常好的优化方法。因为不难发现实际上位数的常数很小,只有 100 不到(骗你的,实际上在 int 范围只有 32)。
因此,这里我的方法和其他大佬不是非常一样:
我们知道,这个数最多有 41 位。那么,问题来了,一个只有 41 位的数最多有多少个
当然是最多 41 个了啊。
我们既然要求的是
变形一下:
注意到:
那么,很显然:
然而,每一个
也就是说,实际上,我们无论如何,只需要将 DP 的
细节
当然,由于
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 5e5 + 10, MOD = 998244353;
ll a [N], bit [N], pre [N], ans [N];
int main () {
int T; scanf ("%d", &T);
while (T --) {
int n; scanf ("%d", &n);
for (int i = 1; i <= n; i++)
scanf ("%lld", a + i);
int lst = 0;
for (int i = 1; i <= n; i++) {
bit [i] = __builtin_popcountll (a [i]);
pre [i] = lst;
if (a [i] != 0)
lst = i;
}
ans [0] = 0;
for (int i = 1; i <= n; i++) {
ll sum = 0, sum_bit = 0, f = 0;
for (int p = i; p != 0; p = pre [p]) {
sum += a [p]; sum_bit += bit [p];
if (sum_bit > 42) break;
if (sum_bit - __builtin_popcountll(sum) == 1) {
f += (ans [p] - ans [pre [p]]) % MOD;
if (f >= MOD) f -= MOD;
}
}
ans [i] = (ans [i - 1] + (f + 1) % MOD + MOD) % MOD;
}
cout << ans [n] << '\n';
}
return 0;
}