洛谷 P5170 【模板】类欧几里得算法(类欧几里得)

洛谷题面传送门

一个挺毒瘤的算法,感觉对于应对 NOI 及以下的考试完全没有用(?),不过学了就当一个技能防身吧 /cy

类欧几里是一种能够高效求解带下取整符号,且枚举变量在分子上的和式的算法。其本质上与传统欧几里得算法一点关联都没有,之所以取名为类欧几里得,是因为其复杂度分析与欧几里得相同,均为单次 Θ(logn)

我们先来看一个类欧几里得最基础的应用:求解 i=0nai+bc,不妨设其为 f(n,a,b,c)

首先特判掉 n<0​ 的情况——结果显然为 0​,以及 a=0​ 的情况,结果为 (n+1)·bc

考虑剩余的情况,分 acbc,以及 a<cb<c 两种情况处理。

对于 acbc​ 的情况,设 a=ac,b=bc,ra=amodc,rb=bmodc,那么

f(n,a,b,c)=i=0nai·c+bc+ra·i+rbc=i=0nai+b+ra·i+rbc=a·n(n+1)2+b·(n+1)+f(n,ra,rb,c)

递归处理即可。

对于 a<c​ 且 b<c​ 的情况,显然有 an+bc<n​,不妨设其为 M​。那么我们就考虑交换下标和值域,即将 AB​ 变为 i=0AB11​,这样有:

f(n,a,b,c)=i=0nj=0M1[j<ai+bc]=j=0M1i=0n[jc+c1<ai+b]=j=0M1i=0n[i>jc+cb1a]=j=0M1njc+cb1a= nMj=0M1jc+cb1a= nMf(M1,c,cb1,a)

可以发现,每次遇到 ac​ 或 bc​ 的情况,必然存在一个常数 λ23,满足 a+b 至多变为原来的 λ 倍,因此前一种情况最多出现 Θ(logV) 次,而遇到 a<cb<c 的情况,必然在进行此次操作以后又变为 acbc 的情况,故后一种情况也最多出现 Θ(logV) 次。也就是说递归层数为 Θ(logV)​ 级别的,进而证明了该算法的复杂度。​

接下来着重研究另外两个函数

g(n,a,b,c)=i=0nai+bc2h(n,a,b,c)=i=0niai+bc

的计算方式。

类比之前 f(n,a,b,c) 的算法,我们先特判掉一些特殊情况:

  • n<0g(n,a,b,c)=h(n,a,b,c)=0
  • a=0g(n,a,b,c)=(n+1)bc2h(n,a,b,c)=n(n+1)2·bc

还是分为 acbca,b<c 的情况。

acbc,那么还是设 a=ac,b=bc,ra=amodc,rb=bmodc

g(n,a,b,c)=i=0nai+bc2=i=0nai·c+b·c+ra·i+rbc2=i=0n(ai+b+ra·i+rbc)2=i=0na2i2+b2+ra·i+rbc2+2ai·ra·i+rbc+2b·ra·i+rbc+2abi=n(n+1)(2n+1)6·a2+(n+1)·b2+2ab·n(n+1)2+g(n,ra,rb,c)+2a·h(n,ra,rb,c)+2b·f(n,ra,rb,c)

h(n,a,b,c)=i=0niai+bc=i=0niai·c+b·c+ra·i+rbc=i=0ni(ai+b+ra·i+rbc)=i=0nai2+bi+i·ra·i+rbc= a·n(n+1)(2n+1)6+b·n(n+1)2+h(n,ra,rb,c)

递归下去即可。

a<cb<c,那么

g(n,a,b,c)=i=0nai+bc2=i=0nj=0M1[j<ai+bc](2j+1)=j=0M1i=0n[j<ai+bc]+2·j=0M1i=0n[j<ai+bc]·j= f(n,a,b,c)+2·j=0M1i=0n[jc+c1<ai+b]·j= f(n,a,b,c)+2·j=0M1j·(njc+cb1a)= f(n,a,b,c)+2nM(M1)2·j=0M1j·jc+cb1a= f(n,a,b,c)+2nM(M1)2·h(M1,c,cb1,a)

h(n,a,b,c)=i=0nai+bc·i=i=0nj=0M1i·[jc+c1<ai+b]=j=0M1i=0ni·[i>jc+cb1a]=j=0M1n(n+1)212·jc+cb1a·(jc+cb1a1)=n(n+1)2·M12j=0M1(jc+cb1a2jc+cb1a)=n(n+1)2·M12(g(M1,c,cb1,a)f(M1,c,cb1,a))

找到了当年推 P5518 [MtOI2019]幽灵乐团 / 莫比乌斯反演基础练习题 的感觉(

直接递归是指数级别的,即 2logn=O(n)​,会爆,可以使用记忆化搜索将其优化到时空复杂度均 O(logn),不过对于此题而言会 MLE。不过注意到三个函数可以并行计算,因此考虑定义一个返回值为 tuple<int, int, int> 的函数一次项返回三个函数的值,这样空间即可做到 O(1),即可通过。

附: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;
}
posted @   tzc_wk  阅读(94)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
历史上的今天:
2021-02-23 Codeforces 505E - Mr. Kitayuta vs. Bamboos(二分+堆)
点击右上角即可分享
微信分享提示