洛谷 P5170 【模板】类欧几里得算法(类欧几里得)
一个挺毒瘤的算法,感觉对于应对 NOI 及以下的考试完全没有用(?),不过学了就当一个技能防身吧 /cy
类欧几里是一种能够高效求解带下取整符号,且枚举变量在分子上的和式的算法。其本质上与传统欧几里得算法一点关联都没有,之所以取名为类欧几里得,是因为其复杂度分析与欧几里得相同,均为单次 。
我们先来看一个类欧几里得最基础的应用:求解 ,不妨设其为 。
首先特判掉 的情况——结果显然为 ,以及 的情况,结果为 。
考虑剩余的情况,分 或 ,以及 且 两种情况处理。
对于 且 的情况,设 ,那么
递归处理即可。
对于 且 的情况,显然有 ,不妨设其为 。那么我们就考虑交换下标和值域,即将 变为 ,这样有:
可以发现,每次遇到 或 的情况,必然存在一个常数 ,满足 至多变为原来的 倍,因此前一种情况最多出现 次,而遇到 且 的情况,必然在进行此次操作以后又变为 或 的情况,故后一种情况也最多出现 次。也就是说递归层数为 级别的,进而证明了该算法的复杂度。
接下来着重研究另外两个函数
的计算方式。
类比之前 的算法,我们先特判掉一些特殊情况:
- ,。
- ,,。
还是分为 或 和 的情况。
若 或 ,那么还是设
递归下去即可。
若 且 ,那么
找到了当年推 P5518 [MtOI2019]幽灵乐团 / 莫比乌斯反演基础练习题 的感觉(
直接递归是指数级别的,即 ,会爆,可以使用记忆化搜索将其优化到时空复杂度均 ,不过对于此题而言会 MLE。不过注意到三个函数可以并行计算,因此考虑定义一个返回值为 tuple<int, int, int>
的函数一次项返回三个函数的值,这样空间即可做到 ,即可通过。
附:MLE 了的代码:
const int MOD = 998244353; const int INV2 = 499122177; const int INV6 = (MOD + 1) / 6; const int BS1 = 1145141919; const int BS2 = 1004535809; const int BS3 = 1000000007; const int BS4 = 924844033; int n, a, b, c; int get1(int x) {return 1ll * x * (x + 1) % MOD * INV2 % MOD;} int get2(int x) {return 1ll * x * (x + 1) % MOD * (x << 1 | 1) % MOD * INV6 % MOD;} int clcF(int n, int a, int b, int c); int clcG(int n, int a, int b, int c); int clcH(int n, int a, int b, int c); unordered_map<ll, int> F, G, H; ll gethash(int n, int a, int b, int c) { return (1ll * n * BS1 + 1ll * a * BS2 + 1ll * b * BS3 + 1ll * c * BS4); } int clcF(int n, int a, int b, int c) { if (n < 0) return 0; if (!a) return 1ll * (n + 1) * (b / c) % MOD; if (F.count(gethash(n, a, b, c))) return F[gethash(n, a, b, c)]; if (a >= c || b >= c) return F[gethash(n, a, b, c)] = (1ll * (a / c) * get1(n) + 1ll * (n + 1) * (b / c) + clcF(n, a % c, b % c, c)) % MOD; else { int M = (1ll * a * n + b) / c; return F[gethash(n, a, b, c)] = (1ll * n * M % MOD - clcF(M - 1, c, c - b - 1, a) + MOD) % MOD; } } int clcG(int n, int a, int b, int c) { if (n < 0) return 0; if (!a) return 1ll * (n + 1) * (b / c) % MOD * (b / c) % MOD; if (G.count(gethash(n, a, b, c))) return G[gethash(n, a, b, c)]; // printf("G(%d, %d, %d, %d)\n", n, a, b, c); if (a >= c || b >= c) { int _a = a / c, _b = b / c, ra = a % c, rb = b % c; return G[gethash(n, a, b, c)] = (1ll * _a * _a % MOD * get2(n) + 1ll * _b * _b % MOD * (n + 1) + clcG(n, ra, rb, c) + 2ll * _a * clcH(n, ra, rb, c) % MOD + 2ll * _b * clcF(n, ra, rb, c) % MOD + 2ll * _a * _b % MOD * get1(n)) % MOD; } else { int M = (1ll * a * n + b) / c; return G[gethash(n, a, b, c)] = (2ll * get1(M - 1) * n % MOD - 2ll * clcH(M - 1, c, c - b - 1, a) % MOD + clcF(n, a, b, c) + MOD) % MOD; } } int clcH(int n, int a, int b, int c) { if (n < 0) return 0; if (!a) return 1ll * get1(n) * (b / c) % MOD; if (H.count(gethash(n, a, b, c))) return H[gethash(n, a, b, c)]; // printf("H(%d, %d, %d, %d)\n", n, a, b, c); if (a >= c || b >= c) { int _a = a / c, _b = b / c, ra = a % c, rb = b % c; return H[gethash(n, a, b, c)] = (1ll * _a * get2(n) + 1ll * _b * get1(n) + clcH(n, ra, rb, c)) % MOD; } else { int M = (1ll * a * n + b) / c; return H[gethash(n, a, b, c)] = (1ll * M * get1(n) % MOD - 1ll * INV2 * clcG(M - 1, c, c - b - 1, a) % MOD - 1ll * INV2 * clcF(M - 1, c, c - b - 1, a) % MOD + MOD + MOD) % MOD; } } void solve() { scanf("%d%d%d%d", &n, &a, &b, &c); printf("%d %d %d\n", clcF(n, a, b, c), clcG(n, a, b, c), clcH(n, a, b, c)); } int main() { int qu; scanf("%d", &qu); while (qu--) solve(); return 0; }
AC 代码:
const int MOD = 998244353; const int INV2 = 499122177; const int INV6 = (MOD + 1) / 6; int n, a, b, c; int get1(int x) {return 1ll * x * (x + 1) % MOD * INV2 % MOD;} int get2(int x) {return 1ll * x * (x + 1) % MOD * (x << 1 | 1) % MOD * INV6 % MOD;} tuple<int, int, int> calc(int n, int a, int b, int c) { if (n < 0) return mt(0, 0, 0); if (!a) return mt( 1ll * (n + 1) * (b / c) % MOD, 1ll * (n + 1) * (b / c) % MOD * (b / c) % MOD, 1ll * get1(n) * (b / c) % MOD); int S1 = get1(n), S2 = get2(n); if (a >= c || b >= c) { int _a = a / c, _b = b / c, ra = a % c, rb = b % c; tuple<int, int, int> T = calc(n, ra, rb, c); return mt((1ll * _a * S1 + 1ll * (n + 1) * _b + get<0>(T)) % MOD, (1ll * _a * _a % MOD * S2 + 1ll * _b * _b % MOD * (n + 1) + get<1>(T) + 2ll * _a * get<2>(T) % MOD + 2ll * _b * get<0>(T) % MOD + 2ll * _a * _b % MOD * S1) % MOD, (1ll * _a * S2 + 1ll * _b * S1 + get<2>(T)) % MOD); } else { int M = (1ll * a * n + b) / c; tuple<int, int, int> T = calc(M - 1, c, c - b - 1, a); int F = (1ll * n * M % MOD - get<0>(T) + MOD) % MOD; int G = (2ll * get1(M - 1) * n % MOD - 2ll * get<2>(T) % MOD + F + MOD) % MOD; int H = (1ll * M * S1 % MOD - 1ll * INV2 * get<1>(T) % MOD - 1ll * INV2 * get<0>(T) % MOD + MOD + MOD) % MOD; return mt(F, G, H); } } void solve() { scanf("%d%d%d%d", &n, &a, &b, &c); tuple<int, int, int> T = calc(n, a, b, c); printf("%d %d %d\n", get<0>(T), get<1>(T), get<2>(T)); } int main() { int qu; scanf("%d", &qu); while (qu--) solve(); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
2021-02-23 Codeforces 505E - Mr. Kitayuta vs. Bamboos(二分+堆)