ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

类欧几里得算法与万能欧几里得算法:OI-wiki 中直线下整点计数与操作序列方法

类欧几里得算法与万能欧几里得算法:OI-wiki 中直线下整点计数与操作序列方法 类欧几里得算法与万能欧几里得算法OI-wiki 中直线下整点计数与操作序列方法【免费下载链接】OI-wiki:star2: Wiki of OI / ICPC for everyone. 某大型游戏线上攻略内含炫酷算术魔法项目地址: https://gitcode.com/GitHub_Trending/oi/OI-wiki本文围绕 OI-wiki 数论模块中的 euclidean.md 展开系统讲解用于计算 $\left\lfloor\dfrac{aib}{c}\right\rfloor$ 形式求和的类欧几里得算法以及将其推广、可求解更多带权和式的万能欧几里得算法。这两类算法利用分数自身的递归结构与欧几里得算法的内在联系将大范围求和问题约化为 $O(\log\min{a,c,n})$ 的递归过程是竞赛编程中处理「直线下方整点计数」类问题的利器。读完本文你将掌握 $f/g/h$ 三类和式的代数推导、几何直观理解以及基于幺半群抽象出的统一递归模板并能在 Library Checker - Sum of Floor of Linear 与 Luogu P5170 等模板题上直接落地实现。引入从欧几里得算法到类欧几里得算法类欧几里得算法由洪华敦在 2016 年冬令营营员交流中提出常用于解决形如$$ \left\lfloor\dfrac{aib}{c}\right\rfloor $$结构的数列下标为 $i$的求和问题。它的核心思想是利用分数自身的递归结构将问题转化为更小规模的问题递归求解。之所以冠以「类欧几里得」之名是因为分数的递归结构与 欧几里得算法 存在直接联系详见 连分数表示的求法。事实上连分数 和 Stern–Brocot 树 等方法同样刻画了分数的递归结构因此能用类欧几里得算法解决的问题通常也可以用这些方法解决但相较之下类欧几里得算法通常更容易理解实现也更为简明。类欧几里得算法最简单的例子是求和问题$$ f(a,b,c,n)\sum_{i0}^n\left\lfloor \frac{aib}{c} \right\rfloor, $$其中 $a,b,c,n$ 都是正整数。代数解法第一步取模约化。将 $a,b$ 对 $c$ 取模可以简化问题将问题转化为 $0\le a,bc$ 的情形$$ \begin{aligned} f(a,b,c,n)\sum_{i0}^n\left\lfloor \frac{aib}{c} \right\rfloor\ \sum_{i0}^n\left\lfloor \frac{\left(\left\lfloor\frac{a}{c}\right\rfloor c(a\bmod c)\right)i\left(\left\lfloor\frac{b}{c}\right\rfloor c(b\bmod c)\right)}{c}\right\rfloor\ \sum_{i0}^n\left(\left\lfloor\frac{a}{c}\right\rfloor i\left\lfloor\frac{b}{c}\right\rfloor\left\lfloor\frac{\left(a\bmod c\right)i\left(b\bmod c\right)}{c} \right\rfloor\right)\ \frac{n(n1)}{2}\left\lfloor\frac{a}{c}\right\rfloor (n1)\left\lfloor\frac{b}{c}\right\rfloorf(a\bmod c,b\bmod c,c,n). \end{aligned} $$其中 $\sum_{i0}^n i\frac{n(n1)}{2}$ 的等差数列求和是唯一的「闭合解」部分。第二步交换求和次序。现在考虑转化后 $0\le a,bc$ 的问题。令$$ m \left\lfloor \frac{anb}{c} \right\rfloor. $$那么原问题可以写作二次求和式$$ \sum_{i0}^n\left\lfloor \frac{aib}{c} \right\rfloor \sum_{i0}^n\sum_{j0}^{m-1}\left[j\left\lfloor \frac{aib}{c} \right\rfloor\right]. $$交换求和次序需要对于每个 $j$ 计算满足条件的 $i$ 的范围。为此将条件变形$$ \begin{aligned} j\left\lfloor \frac{aib}{c} \right\rfloor \left\lceil \frac{aib1}{c} \right\rceil-1\ \iff j 1 \left\lceil \frac{aib1}{c} \right\rceil \iff j1 \frac{aib1}{c} \ \iff \dfrac{cjc-b-1}{a} i \iff \left\lfloor\dfrac{cjc-b-1}{a}\right\rfloor i. \end{aligned} $$变形过程中多次利用了 取整函数 的性质。代入变形后的条件原式可以写作$$ \begin{aligned} f(a,b,c,n)\sum_{j0}^{m-1} \sum_{i0}^n\left[i\left\lfloor\frac{cjc-b-1}{a}\right\rfloor \right]\ \sum_{j0}^{m-1}\left(n-\left\lfloor\frac{cjc-b-1}{a}\right\rfloor\right)\ nm-f\left(c,c-b-1,a,m-1\right). \end{aligned} $$令 $(a,b,c,n)(c,c-b-1,a,m-1)$这就回到了前面讨论过的 $ac$ 的情形。将两步转化结合在一起可以发现过程中 $(a,c)$ 不断地取模后交换位置直到 $a0$这类似于对 $(a,c)$ 进行辗转相除——这正是类欧几里得算法得名的由来其时间复杂度为 $O(\log\min{a,c})$。关于 $m0$ 的边界情形。计算过程中可能出现 $m0$此时内层递归会出现 $n-1$但这不影响最终结果。如果要求出现 $m0$ 时直接终止算法算法的时间复杂度可以改良为 $O(\log\min{a,c,n})$。复杂度的几何解释。利用该算法与欧几里得算法的相似性容易说明其时间复杂度是 $O(\log\min{a,c})$而若在 $m0$ 时终止算法还需说明它也是 $O(\log n)$ 的。令 $m\lfloor(anb)/c\rfloor$记 $Smn$、$km/n$它们分别相当于几何直观中见下一小节点阵图的面积和直线的斜率对于充分大的 $n$近似有 $k\doteq a/c$。考察 $S$ 和 $k$ 在算法过程中的变化第一步取模时 $n$ 保持不变$k$ 近似由 $a/c$ 变为 $(a\bmod c)/c$即斜率由 $k$ 变为 $k-\lfloor k\rfloor$而 $S$ 也近似变为原来的 $(k-\lfloor k\rfloor)$ 倍第二步交换横纵坐标时$S$ 近似保持不变$k$ 变为它的倒数。因此若设两步操作后二元组 $(k,S)$ 变为 $(k,S)$则有 $k(k-\lfloor k\rfloor)^{-1}$ 且 $S(k-\lfloor k\rfloor)S$。因为 $1\le\lfloor k\rfloor\le k\lfloor k\rfloor1$递归计算两轮后乘积缩小的倍数最少为$$ (k-\lfloor k\rfloor)(k-\lfloor k\rfloor) 1-\dfrac{\lfloor k\rfloor}{k} 1-\dfrac{\lfloor k\rfloor}{\lfloor k\rfloor1} \dfrac{1}{\lfloor k\rfloor1}\le \dfrac{1}{2}. $$因此至多 $O(\log S)$ 轮算法必然终止。由于从第二轮开始每轮开始时的 $S$ 总是不超过上一轮取模结束后的 $S$而后者大致为 $kn^2$ 且 $k1$故 $O(\log S)\subseteq O(\log n)$结论得证。模板题参考实现。仓库中的 euclidean-0.cpp 是在 Library Checker - Sum of Floor of Linear 上验证通过的最小实现提交编号见源码头部注释它精确对应上面的两步递归#include iostream long long solve(long long a, long long b, long long c, long long n) { long long n2 n * (n 1) / 2; if (a c || b c) return solve(a % c, b % c, c, n) (a / c) * n2 (b / c) * (n 1); long long m (a * n b) / c; if (!m) return 0; return m * n - solve(c, c - b - 1, a, m - 1); } int main() { int t; std::cin t; for (; t; --t) { int a, b, c, n; std::cin n c a b; std::cout solve(a, b, c, n - 1) \n; } return 0; }注意主函数中读取顺序为n c a b且传入n - 1这是 Library Checker 题目中求和范围 $[0,n)$ 的约定原题下标从 $0$ 到 $n-1$。几何直观类欧几里得算法可以从几何角度理解其主要解决的问题是直线下整点计数问题。如下图中最左部分所示求和式相当于求直线$$ y \dfrac{axb}{c} $$下方、$x$ 轴上方不包括 $x$ 轴、且横坐标位于 $[0,n]$ 之间的格点数目。第一步移除整数部分。这一步相当于将上图中间部分的蓝点数量单独计算出来。当斜率和截距都是整数时蓝点构成梯形阵列——不同纵列的格点形成等差数列数量容易计算。移除这些点后剩余的格点与上图最右部分的红点数量一致问题转化为斜率和截距都小于一的情形。因为梯形的高为 $n1$两个底边长度分别为 $\lfloor b/c\rfloor$ 和 $\lfloor a/c\rfloor n\lfloor b/c\rfloor$利用梯形面积公式可归纳为$$ f(a,b,c,n) f(a\bmod c,b\bmod c,c,n) \dfrac{1}{2}(n1)\left(\left\lfloor\dfrac{b}{c}\right\rfloor\left(\left\lfloor\dfrac{a}{c}\right\rfloor n\left\lfloor\dfrac{b}{c}\right\rfloor\right)\right). $$第二步翻转横纵坐标轴。如下图最左部分所示红点和蓝点构成一个横向长度为 $n$、纵向长度为 $m\lfloor(anb)/c\rfloor$ 的矩形点阵。要计算红点数量只需计算蓝点数量再用矩形点阵总数减去即可。翻转后左半部分的蓝点点阵变成某条直线下方的红色点阵且翻转后斜率大于一又回到上文已处理的情形。关键在于新红色点阵上方直线的方程。将最左部分的横纵坐标轴翻转得到中间部分翻转后的红色点阵上方的直线中间部分实线并非翻转前直线最左部分实线的直接翻转而是向左上平移一点点的结果最左部分虚线。这是因为直接将直线翻转会得到中间部分虚线而按定义它下方的格点包含恰好落在直线上的格点会造成重复计数。为避免这一点需将翻转后得到的直线 $y(cx-b)/a$ 向下平移一点点得到 $y(cx-b-1)/a$这样它下方的点阵才恰为翻转前的蓝色点阵。还有一处细节中间部分直线的截距是负数尚未回到初始情形。要让截距恢复非负只需将直线向左平移一个单位——这不会漏掉任何格点因为翻转前的蓝色点阵中没有纵坐标为零的点翻转后也就不存在横坐标为零的点。最终直线方程变为 $y(cxc-b-1)/a$点阵横坐标上界也从 $m$ 变为 $m-1$。这一步骤归纳为$$ f(a,b,c,n) mn - f(c,c-b-1,a,m-1). $$递归为何必然终止主要有两个原因直线的斜率不断地先取小数部分再取倒数等价于计算斜率 $ka/c$ 的 连分数展开。因为有理分数连分数展开的长度是 $O(\log\min{a,c})$ 的这一过程一定在 $O(\log\min{a,c})$ 步后终止每次翻转坐标轴时直线斜率都小于一直觉上应有 $mn$即每轮迭代横坐标范围都在缩小前文的复杂度分析严格说明每两轮迭代后 $n$ 至多为原来的一半因此该过程一定在 $O(\log n)$ 步后终止。这也是斜率为有理数时类欧几里得算法复杂度为 $O(\log\min{a,c,n})$ 的原因。利用类似的几何直观还可以将类欧几里得算法推广到斜率为无理数的情形见后文例题。例题一【模板】类欧几里得算法Luogu P5170多组询问给定正整数 $a,b,c,n$求$$ \begin{aligned} f(a,b,c,n) \sum_{i0}^n\left\lfloor \frac{aib}{c} \right\rfloor,\ g(a,b,c,n) \sum_{i0}^ni\left\lfloor \frac{aib}{c} \right\rfloor,\ h(a,b,c,n) \sum_{i0}^n\left\lfloor \frac{aib}{c} \right\rfloor^2. \end{aligned} $$推导 $g,h$ 的递归表达式。类似于 $f$ 的推导首先利用取模将问题转化为 $0\le a,bc$ 的情形$$ \begin{aligned} g(a,b,c,n) g(a\bmod c,b\bmod c,c,n)\left\lfloor\frac{a}{c}\right\rfloor\frac{n(n1)(2n1)}{6}\left\lfloor\frac{b}{c}\right\rfloor\frac{n(n1)}{2}, \ h(a,b,c,n)h(a\bmod c,b\bmod c,c,n)\ \quad2\left\lfloor\frac{b}{c}\right\rfloor f(a\bmod c,b\bmod c,c,n) 2\left\lfloor\frac{a}{c}\right\rfloor g(a\bmod c,b\bmod c,c,n)\ \quad\left\lfloor\frac{a}{c}\right\rfloor^2\frac{n(n1)(2n1)}{6}\left\lfloor\frac{b}{c}\right\rfloor^2(n1) \left\lfloor\frac{a}{c}\right\rfloor\left\lfloor\frac{b}{c}\right\rfloor n(n1). \end{aligned} $$然后利用交换求和次序进一步转化。同样令 $m \left\lfloor \frac{anb}{c} \right\rfloor$。对于 $g$$$ \begin{aligned} g(a,b,c,n)\sum_{i0}^ni\left\lfloor \frac{aib}{c} \right\rfloor\ \sum_{i0}^n \sum_{j0}^{m-1}i \left[j\left\lfloor\frac{aib}{c}\right\rfloor\right] \ \sum_{j0}^{m-1}\sum_{i0}^n i\left[i\left\lfloor\frac{cjc-b-1}{a}\right\rfloor \right]\ \sum_{j0}^{m-1}\dfrac{1}{2}\left(\left\lfloor\frac{cjc-b-1}{a}\right\rfloorn1\right)\left(n-\left\lfloor\frac{cjc-b-1}{a}\right\rfloor\right)\ \dfrac{1}{2}mn(n1) - \dfrac{1}{2}\sum_{j0}^{m-1}\left\lfloor\frac{cjc-b-1}{a}\right\rfloor - \dfrac{1}{2}\sum_{j0}^{m-1}\left\lfloor\frac{cjc-b-1}{a}\right\rfloor^2\ \dfrac{1}{2}mn(n1) - \dfrac{1}{2}f(c,c-b-1,a,m-1) - \dfrac{1}{2}h(c,c-b-1,a,m-1). \end{aligned} $$对于 $h$$$ \begin{aligned} h(a,b,c,n)\sum_{i0}^n\left\lfloor \frac{aib}{c} \right\rfloor^2\ \sum_{i0}^n\sum_{j0}^{m-1}(2j1)\left[j\left\lfloor\frac{aib}{c}\right\rfloor\right]\ \sum_{j0}^{m-1}\sum_{i0}^n(2j1)\left[i\left\lfloor\frac{cjc-b-1}{a}\right\rfloor \right]\ \sum_{j0}^{m-1}(2j1)\left(n-\left\lfloor\frac{cjc-b-1}{a}\right\rfloor\right)\ nm^2 - \sum_{j0}^{m-1}\left\lfloor\frac{cjc-b-1}{a}\right\rfloor - 2\sum_{j0}^{m-1}j\left\lfloor\frac{cjc-b-1}{a}\right\rfloor\ nm^2 - f(c,c-b-1,a,m-1) - 2g(c,c-b-1,a,m-1). \end{aligned} $$从几何直观的角度看这些非线性求和式相当于给区域中每个点 $(i,j)$ 赋予相应权重 $w(i,j)$除权重外计算过程完全一致。一般地权重的选择满足$$ \sum_{i0}^ni^r\left\lfloor \frac{aib}{c} \right\rfloor^s \sum_{i0}^n\sum_{j0}^{m-1} i^r\left((j1)^s-j^s\right)\left[j\left\lfloor\frac{aib}{c}\right\rfloor\right]. $$本题的另一个特点是 $g$ 和 $h$ 在递归计算时相互交错因此需要将 $(f,g,h)$ 作为三元组同时递归。仓库中的 euclidean-1.cpp 实现了这一做法它在模 $998244353$ 意义下计算使用 $i2(M1)/2$、$i6(M1)/6$ 处理逆元并在取模约化分支中显式叠加了 $f,g,h$ 三者的交叉项#include iostream struct Data { int f, g, h; }; Data solve(long long a, long long b, long long c, long long n) { constexpr long long M 998244353; constexpr long long i2 (M 1) / 2; constexpr long long i6 (M 1) / 6; long long n2 (n 1) * n % M * i2 % M; long long n3 (2 * n 1) * (n 1) % M * n % M * i6 % M; Data res {0, 0, 0}; if (a c || b c) { auto tmp solve(a % c, b % c, c, n); long long aa a / c, bb b / c; res.f (tmp.f aa * n2 bb * (n 1)) % M; res.g (tmp.g aa * n3 bb * n2) % M; res.h (tmp.h 2 * bb * tmp.f % M 2 * aa * tmp.g % M aa * aa % M * n3 % M bb * bb % M * (n 1) % M 2 * aa * bb % M * n2 % M) % M; return res; } long long m (a * n b) / c; if (!m) return res; auto tmp solve(c, c - b - 1, a, m - 1); res.f (m * n - tmp.f M) % M; res.g (m * n2 (M - tmp.f) * i2 (M - tmp.h) * i2) % M; res.h (n * m % M * m - tmp.f - tmp.g * 2 3 * M) % M; return res; } int main() { int t; std::cin t; for (; t; --t) { int n, a, b, c; std::cin n a b c; auto res solve(a, b, c, n); std::cout res.f res.h res.g \n; } return 0; }注意 $g$ 分支中m * n2 - (tmp.f tmp.h)/2与推导式对应$h$ 分支中n*m*m - tmp.f - 2*tmp.g与推导式对应输出顺序为f h g题目要求。例题二【清华集训 2014】SumLuogu P5172多组询问给定正整数 $n$ 和 $r$求$$ \sum_{d1}^n(-1)^{\lfloor d\sqrt{r}\rfloor}. $$如果 $r$ 是完全平方数当 $\sqrt{r}$ 为偶数时和式为 $n$否则和式依据 $n$ 的奇偶性在 $0$ 和 $-1$ 之间交替变化。下面考虑 $r$ 不是完全平方数的情形。为了应用类欧几里得算法先将求和式转化为熟悉的形式$$ \begin{aligned} \sum_{d1}^n(-1)^{\lfloor d\sqrt{r}\rfloor} \sum_{d1}^n\left(1 - 2(\lfloor d\sqrt{r}\rfloor\bmod 2)\right)\ n - 2\sum_{d1}^n\left(\lfloor d\sqrt{r}\rfloor - 2\left\lfloor\dfrac{\lfloor d\sqrt{r}\rfloor}{2}\right\rfloor\right) \ n - 2\sum_{d1}^n\lfloor d\sqrt{r}\rfloor 4 \sum_{d1}^n\left\lfloor\dfrac{d\sqrt{r}}{2}\right\rfloor\ n - 2f(n,1,0,1) 4f(n,1,0,2) \end{aligned} $$其中函数 $f$ 具有形式$$ f(a,b,c,n) \sum_{i1}^n\left\lfloor\dfrac{a\sqrt{r}b}{c}i\right\rfloor. $$与正文算法不同此处斜率不再是有理数。设斜率 $k \dfrac{a\sqrt{r}b}{c}$分两种情形讨论。若 $k\ge 1$$$ \begin{aligned} f(a,b,c,n) \sum_{i1}^n \lfloor ki\rfloor \sum_{i1}^n \lfloor(k-\lfloor k\rfloor)i\rfloor \lfloor k\rfloor \sum_{i1}^ni\ \lfloor k\rfloor\dfrac{n(n1)}{2} f(a,b-c\lfloor k\rfloor,c,n). \end{aligned} $$问题转化为斜率小于一的情形。若 $k1$设 $m\lfloor nk\rfloor$有$$ \begin{aligned} f(a,b,c,n) \sum_{i1}^n \lfloor ki\rfloor \sum_{i1}^n\sum_{j1}^m[j\le\lfloor ki\rfloor]\ \sum_{j1}^m\sum_{i1}^n[i\lfloor k^{-1}j\rfloor] nm - \sum_{j1}^m\sum_{i1}^n[i\le\lfloor k^{-1}j\rfloor]. \end{aligned} $$此处交换 $i,j$ 的条件比正文更简单是因为直线 $ykx$ 上除原点外没有格点。关键在于将交换后的求和式写成 $f(a,b,c,n)$ 的形式即要求 $a,b,c$ 满足$$ k^{-1} \dfrac{a\sqrt{r}b}{c}. $$分母有理化即可得到$$ k^{-1} \dfrac{c}{a\sqrt{r}b} \dfrac{ca\sqrt{r}-cb}{a^2r-b^2}. $$因此有$$ aca,~b-cb,~ca^2r-b^2, $$即$$ f(a,b,c,n) nm - f(ca,-cb,a^2r-b^2,m). $$数值细节。为避免整数溢出每次都需要将 $a,b,c$ 同除以它们的最大公约数。由于这个计算过程与计算 $k$ 的连分数的过程完全一致根据 连分数理论只要保证 $\gcd(a,b,c)1$它们在计算过程中必然在整型范围内。另外尽管 $(a,b,c,n)$ 不会溢出但在本题数据范围下 $f(a,b,c,n)$ 可能超过 64 位整数范围自然溢出即可无需额外处理——最后结果一定在 $[-n,n]$ 之间。尽管斜率不会变为零算法复杂度仍是 $O(\log n)$ 的。仓库中的 euclidean-2.cpp 实现了上述过程其solve中对完全平方数单独分支、f中先约分 $\gcd(a,b,c)$、用sqrtl计算斜率并据此分支取整读者可对照阅读。例题三FractionLuogu P5179给定正整数 $a,b,c,d$求所有满足 $a/bp/qc/d$ 的最简分数 $p/q$ 中 $(q,p)$ 字典序最小的那个。这道题目同样是 Stern–Brocot 树 的经典应用相关题解可在 连分数的树 找到。因为它只依赖于分数的递归结构也可以用类似欧几里得算法的方法求解故可视作类欧几里得算法的应用。如果 $a/b$ 和 $c/d$ 之间不含端点存在至少一个自然数可直接取 $(q,p)(1,\lfloor a/b\rfloor1)$。否则必然有$$ \left\lfloor\dfrac{a}{b}\right\rfloor \le \dfrac{a}{b} \dfrac{p}{q} \dfrac{c}{d}\le\left\lfloor\dfrac{a}{b}\right\rfloor1. $$从这个不等式可以看出 $p/q$ 的整数部分可确定为 $\lfloor a/b\rfloor$直接消去整数部分后整体取倒数用于确定小数部分——这正是确定 $p/q$ 的连分数的 基本方法。若最终答案是 $p/q$算法时间复杂度为 $O(\log\min{p,q})$。字典序细节。需要确认取倒数之后得到的字典序最小的分数是否也是取倒数之前的字典序最小分数。即满足 $a/bp/qc/d$ 的分数 $p/q$ 中字典序 $(q,p)$ 最小的是否也是字典序 $(p,q)$ 最小的。反设 $p/q$ 是字典序 $(q,p)$ 最小的但 $r/s\neq p/q$ 是字典序 $(r,s)$ 最小的则必有 $rp$ 且 $qs$。但这说明$$ \dfrac{a}{b} \dfrac{r}{s} \dfrac{r}{q} \dfrac{p}{q} \dfrac{c}{d}, $$即 $r/q$ 无论按哪个字典序都严格更小与所设矛盾。因此上述算法是正确的。仓库中的 euclidean-3.cpp 给出了极简实现递归中交换 $p,q$ 与 $a,b$ 的角色并在回溯时恢复整数部分。万能欧几里得算法上一节的类欧几里得算法推导通常较为繁琐且能解决的和式主要是可转化为直线下带权整点计数问题的和式。本节讨论更一般的方法——万能欧几里得算法它进一步抽象了上述过程可以解决更多问题。它同样利用分数的递归结构求解但与类欧几里得算法约化问题的思路稍有不同。仍考虑最经典的求和问题$$ f(a,b,c,n)\sum_{i1}^n\left\lfloor \frac{aib}{c} \right\rfloor, $$其中 $a,b,c,n$ 都是正整数。问题转化操作序列与幺半群设参数为 $(a,b,c,n)$ 的线段为$$ y \frac{axb}{c},~0 x\le n. $$对于这条线段可以定义一个由 $U$ 和 $R$ 组成的字符串 $S$称为操作序列字符串恰有 $n$ 个 $R$ 和 $m\lfloor(anb)/c\rfloor$ 个 $U$第 $i$ 个 $R$ 前方的 $U$ 的数量恰等于 $\lfloor(aib)/c\rfloor$其中 $i1,\cdots,n$。从几何直观上看这相当于从原点开始每向右穿过一次竖向网格线写下 $R$每向上穿过一次横向网格线写下 $U$如下图所示这样定义还需考量一系列特殊情形经过整点同时上穿和右穿时先写 $U$ 再写 $R$字符串开始时除在 $(0,1]$ 区间内上穿网格线的次数外还需额外补充 $\lfloor b/c\rfloor$ 个 $U$字符串结束时不能有额外的 $U$。如果几何直观描述有不明晰之处可参考上述代数方法的定义辅助理解。万能欧几里得算法的基本思路将操作序列中的 $U$ 和 $R$ 都视作某个 幺半群 内的元素将整个操作序列视为幺半群内元素的乘积最终答案与这个乘积有关。方式一矩阵。以本题为例定义状态向量 $v(1,y,\sum y)$表示自原点开始经历若干次上穿和右穿后的状态第一个分量是常数第二个是纵坐标 $y$第三个是要求的和式。起始时 $v(1,0,0)$。每向上穿过一次网格线纵坐标累加一相当于状态向量右乘矩阵$$ U \begin{pmatrix}1 1 0 \ 0 1 0 \ 0 0 1\end{pmatrix}. $$每向右穿过一次和式累加一次纵坐标相当于右乘矩阵$$ R \begin{pmatrix}1 0 0 \ 0 1 1 \ 0 0 1\end{pmatrix}. $$最终状态即乘积 $(1,0,0)S$$S$ 为上述矩阵的乘积所求答案就是最终状态的第三个分量。方式二贡献合并更实用。除了矩阵还可以将幺半群元素定义为一段操作序列对最终结果的贡献将操作乘积定义为两段贡献的合并。本题中定义每段操作序列的贡献为 $(x,y,\sum y)$其中 $x(S)$、$y(S)$ 分别对应 $S$ 中 $R$ 和 $U$ 的数量最后一项的求和符号一般定义如下对于操作序列上的函数 $f(S)$定义$$ \sum_S f : \sum{f(S_{[1,r]}):S_rR}, $$其中 $S_r$ 是 $S$ 中第 $r$ 个字符$S_{[1,r]}$ 是前 $r$ 个字符组成的前缀即对操作序列中所有以 $R$ 结尾的前缀求和。例如$$ \sum_S 1 x,~ \sum_S x \dfrac{1}{2}x(x1). $$而 $\sum y$ 就是每次右穿时之前上穿次数的累加对于整段操作序列$y$ 在所有以 $R$ 结尾的前缀处的值正是 $i1,\cdots,n$ 处的所有 $\lfloor(aib)/c\rfloor$ 值因此整段序列的 $\sum y$ 就是题目所求量。初始时 $U(0,1,0)$$R(1,0,0)$。两个元素 $(x_1,y_1,s_1)$ 与 $(x_2,y_2,s_2)$ 的乘积定义为$$ (x_1,y_1,s_1)\cdot (x_2,y_2,s_2) (x_1x_2,y_1y_2,s_1s_2x_2y_1), $$最后一项由$$ \sum_{S_1S_2}y \sum_{S_1}y \sum_{S_2}(yy_1) \sum_{S_1}y \sum_{S_2}y y_1\sum_{S_2}1 s_1s_2x_2y_1 $$得到。容易验证该乘法满足结合律且幺元为 $(0,0,0)$故这些元素在该乘法下构成幺半群所求答案即乘积的第三个分量。两种方法都能得到正确结果但矩阵运算保留了较多冗余信息、常数较大因此第二种方法贡献合并在实际问题中更为实用。算法过程分批次合并操作与类欧几里得算法整体约化不同万能欧几里得算法约化问题的手段是将操作分批次合并。记字符串对应的操作的乘积为 $F(a,b,c,n,U,R)$约化过程如下情形一$b\ge c$。操作序列开始有 $\lfloor b/c\rfloor$ 个 $U$直接计算其乘积并移除。此时第 $i$ 个 $R$ 前方的 $U$ 数量等于$$ \left\lfloor\dfrac{aib}{c}\right\rfloor - \left\lfloor\dfrac{b}{c}\right\rfloor \left\lfloor\dfrac{ai(b\bmod c)}{c}\right\rfloor, $$相当于线段参数由 $(a,b,c,n)$ 变为 $(a,b\bmod c,c,n)$。因此$$ F(a,b,c,n,U,R) U^{\lfloor b/c\rfloor}F(a,b\bmod c,c,n,U,R). $$情形二$a\ge c$。每个 $R$ 前方都至少有 $\lfloor a/c\rfloor$ 个 $U$可将其合并到 $R$ 上即用 $U^{\lfloor a/c\rfloor}R$ 替代 $R$。合并后第 $i$ 个 $R$ 前方的 $U$ 数量等于$$ \left\lfloor\dfrac{aib}{c}\right\rfloor - \left\lfloor\dfrac{a}{c}\right\rfloor i \left\lfloor\dfrac{(a\bmod c)ib}{c}\right\rfloor, $$相当于参数由 $(a,b,c,n)$ 变为 $(a\bmod c,b,c,n)$。因此$$ F(a,b,c,n,U,R) F(a\bmod c,b,c,n,U,U^{\lfloor a/c\rfloor}R). $$情形三其余情形翻转横纵坐标。这基本是在交换 $U$ 和 $R$但翻转后的参数需要仔细计算。结合操作序列定义需确定系数 $(a,b,c,n)$ 使变换前的操作序列中第 $j$ 个 $U$ 前方的 $R$ 数量恰为 $\lfloor(ajb)/c\rfloor$ 且总共有 $n$ 个 $U$。根据定义$$ n\left\lfloor\dfrac{anb}{c}\right\rfloor m, $$而第 $j$ 个 $U$ 前方的 $R$ 数量等于最大的 $i$ 使得$$ \begin{aligned} \left\lfloor\dfrac{aib}{c}\right\rfloor j \iff \dfrac{aib}{c} j \iff i \dfrac{cj-b}{a} \ \iff i \left\lceil\dfrac{cj-b}{a}\right\rceil \left\lfloor\dfrac{cj-b - 1}{a}\right\rfloor 1. \end{aligned} $$因此 $i \lfloor(cj-b-1)/a\rfloor$。这一推导与前文类欧几里得算法类似同样利用了上下取整函数的性质。有两处细节需要处理负截距。截距项 $-(b1)/a$ 为负数。注意到将线段向左平移一个单位可使截距恢复非负因为总有 $(c-b-1)/a\ge 0$。因此可将交换前的第一段 $R^{\lfloor(c-b-1)/a\rfloor}U$ 提取出来只交换剩余操作序列中的 $U$ 和 $R$结尾多余的 $U$。交换 $U$ 和 $R$ 后结尾存在多余的 $U$因此交换前需先将最后一段 $R$ 提取出来其数量为 $n-\lfloor(cm-b-1)/a\rfloor$只交换剩余部分。去掉头尾若干字符后第 $j$ 个 $U$ 前方的 $R$ 数量变为$$ \left\lfloor\dfrac{c(j1)-b-1}{a}\right\rfloor - \left\lfloor\dfrac{c-b-1}{a}\right\rfloor \left\lfloor\dfrac{cj(c-b-1)\bmod a}{a}\right\rfloor. $$回忆起交换前的序列中 $U$ 的数量为 $m\lfloor(anb)/c\rfloor$而左移操作要求交换前至少存在一个 $U$即 $m0$。据此分两种情形$m0$处理上述两点后交换完 $U$ 和 $R$ 的操作序列就是参数为 $(c,(c-b-1)\bmod a,a,m-1)$ 的线段的合法序列所以$$ F(a,b,c,n,U,R) R^{\lfloor(c-b-1)/a\rfloor}UF(c,(c-b-1)\bmod a,a,m-1,R,U)R^{n-\lfloor(cm-b-1)/a\rfloor}. $$$m0$交换前的操作序列只包含 $n$ 个 $R$无需交换直接返回$$ F(a,b,c,n,U,R) R^n. $$与类欧几里得算法不同万能欧几里得算法的这一特殊情形需要单独处理否则会因涉及负幂次而无法正确计算。利用这些讨论即可递归求解。复杂度。假设幺半群内元素单次相乘为 $O(1)$且元素幂次计算都使用 快速幂最终算法复杂度为 $O(\log\max{a,c}\log(b/c))$。复杂度论证的要点是除第一轮迭代外都有 $bc$每轮迭代涉及三次快速幂其总复杂度为$$ O\left(\log\left\lfloor\dfrac{a}{c}\right\rfloor\log\left\lfloor\dfrac{c-b_1-1}{a_1}\right\rfloor\log\left(n-\left\lfloor\dfrac{cm-b_1-1}{a_1}\right\rfloor\right)\right), $$其中 $a_1a\bmod c$、$b_1b\bmod c$ 且 $m\lfloor(a_1nb_1)/c\rfloor$。后两项分别有估计$$ \begin{aligned} \dfrac{c-b_1-1}{a_1} \le \dfrac{c}{a_1},\ n-\left\lfloor\dfrac{cm-b_1-1}{a_1}\right\rfloor \le n - \dfrac{cm-b_1-1}{a_1} 1 \ \le n - \dfrac{c((a_1nb_1)/c-1)-b_1-1}{a_1} 1 \ \dfrac{c1}{a_1}1, \end{aligned} $$故这两项复杂度都是 $O(\log(c/a_1))$。每一轮迭代中线段参数由 $(a,\cdot,c,\cdot)$ 变换为 $(c,\cdot,a\bmod c,\cdot)$该轮总时间复杂度为$$ O\left(\log\dfrac{a}{c}\log\dfrac{c}{a\bmod c}\right), $$全部递归轮次中这些项可以裂项相消总和为 $O(\log a\log c)O(\log\max{a,c})$。再加上第一轮迭代中 $U^{\lfloor b/c\rfloor}$ 的快速幂复杂度 $O(\log(b/c))$即得总复杂度 $O(\log\max{a,c}\log(b/c))$。关于 $O(\log(b/c))$ 项的注记。通常考虑的问题中 $b$ 与 $a$ 同阶这一项可以忽略而且如果在调用万能欧几里得算法前先进行一轮类欧几里得算法的取模消除 $b$ 的影响该项快速幂的复杂度可以规避。这其实是因为通常问题中 $U$ 的初始形式较为特殊其幂次有更简单的形式不需要通过快速幂计算——比如正文例子中 $U^{\lfloor b/a\rfloor}$ 的结果就是将 $U$ 中不在对角线上的那个 $1$ 替换为 $\lfloor b/a\rfloor$无需快速幂。统一模板。万能欧几里得算法的流程可以写成统一模板处理具体问题时只需更改模板类型T的实现。仓库中的 euclidean-4.cpp 完整实现了这一模板其核心euclid函数文件内标记为euclidean片段与上述三种情形一一对应// Class T implements the monoid. // Assume that it provides a multiplication operator // and a default constructor returning the unity in the monoid. // Binary exponentiation. template typename T T pow(T a, int b) { T res; for (; b; b 1) { if (b 1) res res * a; a a * a; } return res; } // Universal Euclidean algorithm. template typename T T euclid(int a, int b, int c, int n, T U, T R) { if (b c) return pow(U, b / c) * euclid(a, b % c, c, n, U, R); if (a c) return euclid(a % c, b, c, n, U, pow(U, a / c) * R); auto m ((long long)a * n b) / c; if (!m) return pow(R, n); return pow(R, (c - b - 1) / a) * U * euclid(c, (c - b - 1) % a, a, m - 1, R, U) * pow(R, n - (c * m - b - 1) / a); }该文件通过#define MATRIX在「矩阵方式」3×3 矩阵的MatrixN类型与「贡献合并方式」Info{x,y,s}类型乘法为 $(x_1x_2,y_1y_2,s_1s_2x_2y_1)$之间切换两者均已在 Library Checker 验证通过。利用此模板模板题 Library Checker - Sum of Floor of Linear 的实现只需分别给出 $U(0,1,0)$、$R(1,0,0)$贡献合并版或矩阵版 $U$、$R$再调用euclid并取出结果的第三个分量。例题四【模板】类欧几里得算法Luogu P5170万能欧几里得版为应用万能欧几里得算法模板首先将 $i0$ 的项提出来单独考虑。剩余部分可看作对参数为 $(a,b,c,n)$ 的线段分别计算 $\sum y,\sum xy,\sum y^2$。与正文一致有两种将操作序列转换为幺半群元素的方式。矩阵运算。状态向量定义为 $(1,x,y,xy,y^2,\sum y,\sum xy,\sum y^2)$初始状态为 $(1,0,0,0,0,0,0,0)$两个操作分别为$$ U \begin{pmatrix} 1 0 1 0 1 0 0 0 \ 0 1 0 1 0 0 0 0 \ 0 0 1 0 2 0 0 0 \ 0 0 0 1 0 0 0 0 \ 0 0 0 0 1 0 0 0 \ 0 0 0 0 0 1 0 0 \ 0 0 0 0 0 0 1 0 \ 0 0 0 0 0 0 0 1 \end{pmatrix},~ R \begin{pmatrix} 1 1 0 0 0 0 0 0 \ 0 1 0 0 0 0 0 0 \ 0 0 1 1 0 1 1 0 \ 0 0 0 1 0 0 1 0 \ 0 0 0 0 1 0 0 1 \ 0 0 0 0 0 1 0 0 \ 0 0 0 0 0 0 1 0 \ 0 0 0 0 0 0 0 1 \end{pmatrix}. $$最终答案为初始状态右乘这些操作矩阵的乘积得到的向量末尾三个分量。这一做法常数巨大并不能通过本题给出细节仅是为了辅助理解。贡献合并。一段操作序列的贡献定义为 $(x,y,\sum y,\sum xy,\sum y^2)$两个操作分别为$$ U (0,1,0,0,0),~ R (1,0,0,0,0). $$贡献合并时$$ \begin{aligned} \sum_{S_1S_2} y \sum_{S_1}y \sum_{S_2}(yy_1) \sum_{S_1}y \sum_{S_2}y x_2y_1,\ \sum_{S_1S_2} xy \sum_{S_1}xy \sum_{S_2}(xx_1)(yy_1) \ \sum_{S_1}xy \sum_{S_2}xy x_1\sum_{S_2}y y_1\sum_{S_2}x x_1y_1\sum_{S_2}1\ \sum_{S_1}xy \sum_{S_2}xy x_1\sum_{S_2}y \dfrac{1}{2}x_2(x_21)y_1 x_1x_2y_1,\ \sum_{S_1S_2}y^2 \sum_{S_1}y^2 \sum_{S_2}(yy_1)^2 \ \sum_{S_1}y^2 \sum_{S_2}y^2 2y_1\sum_{S_2}y y_1^2\sum_{S_2}1 \ \sum_{S_1}y^2 \sum_{S_2}y^2 2y_1\sum_{S_2}y x_2y_1^2. \end{aligned} $$这说明应将操作的乘法定义为$$ \begin{aligned} (x_1,y_1,s_1,t_1,u_1)\cdot(x_2,y_2,s_2,t_2,u_2)\ (x_1x_2,y_1y_2,s_1s_2x_2y_1,\ \qquad t_1t_2x_1s_2(1/2)x_2(x_21)y_1x_1x_2y_1,\ \qquad u_1u_22y_1s_2x_2y_1^2). \end{aligned} $$虽然直接验证较为繁琐但上述贡献向量在该乘法下确实构成幺半群单位元为 $(0,0,0,0,0)$。一般情形。有$$ \begin{aligned} \sum_{S_1S_2}x^ry^s \sum_{S_1}x^ry^s \sum_{S_2}(xx_1)^r(yy_1)^s \ \sum_{S_1}x^ry^s \sum_{i0}^r\sum_{j0}^s\binom{r}{i}\binom{s}{j}x_1^{r-i}y_1^{s-j}\sum_{S_2}x^iy^j. \end{aligned} $$只要维护好所有更低幂次的贡献就可以计算一般情形的和式。仓库中的 euclidean-5.cpp 实现了本解法Info结构维护五元组 $(x,y,s,t,u)$在模 $998244353$ 下实现上述乘法对应代码中的tmp (rhs.x * (rhs.x 1) / 2 x * rhs.x) % M; res.t ...; res.u ...并在主函数中用b / c单独补回 $i0$ 项f res.s b/c、h res.u (b/c)^2输出顺序同样为f h g。例题五【清华集训 2014】SumLuogu P5172万能欧几里得版单独处理 $r$ 为完全平方数的情形与前文完全一致从略仅考虑 $r$ 非完全平方数的情形。本题应用万能欧几里得算法的方式有很多。例如可以为每个操作定义一个线性变换$$ U(x) -x,~ R(x) x 1, $$操作的乘法定义为线性变换的复合最终答案就是操作序列对应的变换的复合函数在 $x0$ 处的值。还可以为每段操作序列定义贡献为 $((-1)^y,\sum(-1)^y)$两个操作分别取$$ U (0,-1),~ R (1,1), $$贡献合并定义为$$ (u_1,v_1)\cdot(u_2,v_2) (u_1u_2,v_1u_1v_2), $$容易验证该乘法下所有操作构成幺半群单位元为 $(0,1)$最终答案是所有元素乘积的第二个分量。这两种方法是一致的如果将线性变换写作 $f(x)uvx$那么线性变换复合对应的系数变化恰恰就是上述操作的乘法——这两个幺半群是同构的。本题中线段的参数为 $(k,n)$其中 $k\in\mathbf R$ 为直线斜率。设操作序列对应的乘积为 $F(k,n,U,R)$递归算法如下若 $k\ge 1$每个 $R$ 前方都有至少 $\lfloor k\rfloor$ 个 $U$所以$$ F(k,n,U,R) F(k-\lfloor k\rfloor,n,U,U^{\lfloor k\rfloor} R). $$若 $k1$交换操作序列中的 $U$ 和 $R$并舍去末尾的 $U$即交换前的 $R$所以$$ F(k,n,U,R) F(k^{-1},m,R,U)R^{n-\lfloor k^{-1}m\rfloor}. $$算法中 $k$ 的迭代过程其实就是在求 $\sqrt{r}$ 的连分数展开为此可以应用 PQa 算法求连分数的过程和万能欧几里得算法迭代的过程可以同时进行。和类欧几里得算法一致算法复杂度仍是 $O(\log n)$ 的。仓库中的 euclidean-6.cpp 实现了这一解法LinearTransform{u,v}类型以eval(x)uv*x求值乘法对应复合主循环中P,Q维护二次无理数连分数展开的状态量PQa 算法核心a(Psqr)/Q为每轮连分数项pow(U,a)*R完成 $U^{\lfloor k\rfloor}R$ 的合并nm完成横坐标收缩并在n归零后退出循环。习题推荐模板题Library Checker - Sum of Floor of LinearLuogu P5170【模板】类欧几里得算法Luogu P5171 EarthquakeLuogu P5172 [清华集训 2014] SumLuogu P4132 [BJOI2012] 算不出的等式LOJ 138. 类欧几里得算法LOJ 6440. 万能欧几里得Luogu P5179 FractionCodeforces 1182 F. Maximum Sine应用题Luogu P4433 [COCI 2009/2010 #1] ALADINAtCoder Beginner Contest 372 G - Ax By CAtCoder Beginner Contest 313 G - Redistribution of PilesAtCoder Beginner Contest 283 Ex - Popcount SumCodeforces 1098 E. Fedya the PotterCodeforces 868 G. El Toll Caves小结两类算法的定位类欧几里得算法与万能欧几里得算法共享同一个数学内核——分数的递归结构。前者以「取模 交换坐标」两步直接约化求和式适合 $f,g,h$ 这类可写成带权直线下整点计数的和式推导直接但每换一种和式都要重新推导后者将操作序列抽象为幺半群元素通过「提取整段 $U$ / 合并 $U^{\lfloor a/c\rfloor}R$ / 翻转坐标并交换 $U,R$」三种约化手段给出统一递归框架只需替换幺半群类型T即可覆盖 $\sum y,\sum xy,\sum y^2$ 乃至一般 $\sum x^ry^s$ 的任意组合甚至能处理斜率为无理数二次无理数的情形。在 OI 与 ICPC 实战中掌握本文 euclidean.md 中的代数推导与几何直观并能在 euclidean-4.cpp 的模板基础上定制T即可覆盖上述模板题与应用题的绝大多数场景。【免费下载链接】OI-wiki:star2: Wiki of OI / ICPC for everyone. 某大型游戏线上攻略内含炫酷算术魔法项目地址: https://gitcode.com/GitHub_Trending/oi/OI-wiki创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进