1 条题解
-
0
CF2072G 题解
1. 题目分析
题意简述
给定 ,对所有 求 的和。其中 表示把 的 进制表示反转后,再转回十进制。
难点剖析
最大可达 ,不能枚举所有进制。另一方面 ,这提示我们应该围绕 而不是 分段。
有一个很关键的特判:当 时, 在 进制下只有一位,所以反转后仍然是 。因此所有 的贡献可以一次性计算。
2. 从暴力到正解
策略一:直接枚举进制
最直接的做法是枚举 到 ,每次模拟进制转换和反转。一次反转需要 ,总复杂度大约是 。
这个做法只适合 很小的情况。由于 可以达到 ,显然不能通过。
策略二:按表示长度分块
如果 且 ,那么 在 进制下最多只有两位。设
$$n=qp+r,\quad q=\left\lfloor\frac np\right\rfloor,\quad r=n\bmod p$$则 的 进制表示是 ,反转后是 ,即
因此只要能在一段区间内让 保持不变,就可以用平方和公式批量计算贡献。
3. 正解思路
如何想到正解
瓶颈在于进制 太多,但当 变大时, 的 进制位数会迅速减少。于是按 和 的关系分成三类:
- :进制较小,数量只有 ,直接模拟。
- :表示最多两位,用整除分块计算。
- :表示只有一位,每个贡献都是 。
对于第二类,固定 后,所有满足该 的 构成一个连续区间 ,其中 。在这个区间内贡献和为
$$\sum_{p=L}^{R}(np-qp^2+q) =n\sum_{p=L}^{R}p-q\sum_{p=L}^{R}p^2+q(R-L+1)$$使用等差数列求和与平方和公式即可。
算法步骤
- 令 。
- 若 ,先加入 ,表示所有 的贡献。
- 对 到 直接模拟 。
- 对 到 做整除分块:
- 当前左端点为 ;
- 令 ;
- 当前块右端点为 ;
- 用公式加入这一整段贡献;
- 令 继续。
正确性说明
对于 , 在 进制下是一位数,反转不改变数值,所以贡献恒为 。
对于 ,算法逐个模拟定义,因此贡献正确。
对于 ,有 ,所以 的 进制表示至多两位。设 ,反转后数值为 ,代入 得到 。整除分块保证同一块内 不变,因此可以用求和公式一次性算出这一块的总贡献,且所有块不重不漏覆盖整个区间。
综上,三类区间覆盖了所有 ,每一类贡献都被正确计算。
复杂度分析
直接模拟部分有 个进制。整除分块部分也只有 个块。每个测试用例的时间复杂度为 ,其中 来自小进制下的反转模拟;空间复杂度为 。
4. 参考代码(C++14)
#include <bits/stdc++.h> using namespace std; using int64 = long long; const int64 MOD = 1000000007LL; const int64 INV2 = 500000004LL; const int64 INV6 = 166666668LL; int64 add_mod(int64 a, int64 b) { a %= MOD; b %= MOD; int64 res = a + b; if (res >= MOD) res -= MOD; return res; } int64 sub_mod(int64 a, int64 b) { a %= MOD; b %= MOD; int64 res = a - b; if (res < 0) res += MOD; return res; } int64 mul_mod(int64 a, int64 b) { return (a % MOD) * (b % MOD) % MOD; } int64 sum_p(int64 r) { if (r <= 0) return 0; return mul_mod(mul_mod(r, r + 1), INV2); } int64 sum_p(int64 l, int64 r) { if (l > r) return 0; return sub_mod(sum_p(r), sum_p(l - 1)); } int64 sum_p2(int64 r) { if (r <= 0) return 0; return mul_mod(mul_mod(mul_mod(r, r + 1), 2 * r + 1), INV6); } int64 sum_p2(int64 l, int64 r) { if (l > r) return 0; return sub_mod(sum_p2(r), sum_p2(l - 1)); } // 按定义模拟 rev(n,p),用于小进制部分。 int64 rev_base(int64 n, int64 p) { int64 res = 0; while (n > 0) { res = add_mod(mul_mod(res, p), n % p); n /= p; } return res; } int64 direct_sum(int64 n, int64 r) { int64 res = 0; for (int64 p = 2; p <= r; ++p) { res = add_mod(res, rev_base(n, p)); } return res; } // 处理 sqrt(n)<p<=n,此时 n 的 p 进制表示最多两位。 int64 two_digit_sum(int64 n, int64 l, int64 r) { if (l > r) return 0; int64 res = 0; int64 L = l; while (L <= r) { int64 q = n / L; int64 R = min(r, n / q); // rev(n,p)=n*p-q*p^2+q,q 在 [L,R] 内不变。 int64 part = mul_mod(n, sum_p(L, R)); part = sub_mod(part, mul_mod(q, sum_p2(L, R))); part = add_mod(part, mul_mod(q, R - L + 1)); res = add_mod(res, part); L = R + 1; } return res; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { int64 n, k; cin >> n >> k; // 计算 floor(sqrt(n)),再微调避免浮点误差。 int64 sq = sqrtl((long double)n); while ((sq + 1) * (sq + 1) <= n) ++sq; while (sq * sq > n) --sq; int64 ans = 0; // p>n 时只有一位,贡献恒为 n。 if (k > n) { ans = add_mod(ans, mul_mod(k - n, n)); } // 小进制直接模拟。 ans = add_mod(ans, direct_sum(n, min(k, sq))); // 大于 sqrt(n) 且不超过 n 的部分使用整除分块。 ans = add_mod(ans, two_digit_sum(n, sq + 1, min(k, n))); cout << ans << '\n'; } return 0; }
- 1
信息
- ID
- 174
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 5
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者