zoukankan      html  css  js  c++  java
  • 【算法学习笔记】类欧几里得算法

    一个基础的数论问题。

    试求 (sum_{i=0}^nleftlfloor frac{ai+b}{c} ight floor) 的值,其中:(a,b ge 0)(n,c >0)

    在Atcoder的AC库中有这样一个函数可以在 (mathcal{O}(lg(n + c + a + b))) 的时间内解决问题。

    函数代码 ↓

    using ll = long long;
    ll floor_sum(ll n, ll m, ll a, ll b) {
        ll ans = 0;
        if (a >= m) {
            ans += (n - 1) * n * (a / m) / 2;
            a %= m;
        }
        if (b >= m) {
            ans += n * (b / m);
            b %= m;
        }
    
        ll y_max = (a * n + b) / m, x_max = (y_max * m - b);
        if (y_max == 0) return ans;
        ans += (n - (x_max + a - 1) / a) * y_max;
        ans += floor_sum(y_max, a, m, (a - x_max % a) % a);
        return ans;
    }
    

    好奇它的证明过程,然后在 OI wiki 上找到相应文档,这个算法名为:类欧几里德算法

    个人证明

    另外补上个人证明:

    (a ge c)(bge c) 时,

    [f(a,b,c,n) = frac{n(n + 1)}{2}*leftlfloor frac{a}{c} ight floor + (n + 1) * leftlfloor frac{b}{c} ight floor + f(a mod c,b mod c,c,n). ]

    (a < c) 并且 (b < c) 时,

    (m = leftlfloor frac{an+b}{c} ight floor)

    [f(a,b,c,n) = mn - f(c,c - b - 1,a,m-1). ]

    然后递归至 (a = 0) 即可.

    例题

    luoguP5171 Earthquake

    数形结合, 把式子稍微简单转换一下, 套用类欧几里得算法即可.

    引入

    [f(a,b,c,n)=sum_{i=0}^nleftlfloor frac{ai+b}{c} ight floor ]

    其中 (a,b,c,n) 是常数。需要一个 (O(log n)) 的算法。

    这个式子和我们以前见过的式子都长得不太一样。带向下取整的式子容易让人想到数论分块,然而数论分块似乎不适用于这个求和。但是我们是可以做一些预处理的。

    如果说 (age c) 或者 (bge c),意味着可以将 (a,b)(c) 取模以简化问题:

    [egin{split} f(a,b,c,n)&=sum_{i=0}^nleftlfloor frac{ai+b}{c} ight floor\ &=sum_{i=0}^nleftlfloor frac{left(leftlfloorfrac{a}{c} ight floor c+amod c ight)i+left(leftlfloorfrac{b}{c} ight floor c+bmod c ight)}{c} ight floor\ &=frac{n(n+1)}{2}leftlfloorfrac{a}{c} ight floor+(n+1)leftlfloorfrac{b}{c} ight floor+ sum_{i=0}^nleftlfloorfrac{left(amod c ight)i+left(bmod c ight)}{c} ight floor\ &=frac{n(n+1)}{2}leftlfloorfrac{a}{c} ight floor +(n+1)leftlfloorfrac{b}{c} ight floor+f(amod c,bmod c,c,n) end{split} ]

    那么问题转化为了 (a<c,b<c) 的情况。观察式子,你发现只有 (i) 这一个变量。因此要推就只能从 (i) 下手。在推求和式子中有一个常见的技巧,就是条件与贡献的放缩与转化。具体地说,在原式 (displaystyle f(a,b,c,n)=sum_{i=0}^nleftlfloor frac{ai+b}{c} ight floor) 中,(0le ile n) 是条件,而 (leftlfloor dfrac{ai+b}{c} ight floor) 是对总和的贡献。

    要加快一个和式的计算过程,所有的方法都可以归约为 贡献合并计算。但你发现这个式子的贡献难以合并,怎么办?将贡献与条件做转化 得到另一个形式的和式。具体地,我们直接把原式的贡献变成条件:

    [sum_{i=0}^nleftlfloor frac{ai+b}{c} ight floor =sum_{i=0}^nsum_{j=0}^{leftlfloor frac{ai+b}{c} ight floor-1}1\ ]

    现在多了一个变量 (j),既然算 (i) 的贡献不方便,我们就想办法算 (j) 的贡献。因此想办法搞一个和 (j) 有关的贡献式。这里有另一个家喻户晓的变换方法,笔者概括为限制转移。具体来说,在上面的和式中 (n) 限制 (i) 的上界,而 (i) 限制 (j) 的上界。为了搞 (j),就先把 j 放到贡献的式子里,于是我们交换一下 (i,j) 的求和算子,强制用 (n) 限制 (j) 的上界。

    [egin{split} &=sum_{j=0}^{leftlfloor frac{an+b}{c} ight floor-1} sum_{i=0}^nleft[j<leftlfloor frac{ai+b}{c} ight floor ight]\ end{split} ]

    这样做的目的是让 (j) 摆脱 (i) 的限制,现在 (i,j) 都被 (n) 限制,而贡献式看上去是一个条件,但是我们仍把它叫作贡献式,再对贡献式做变换后就可以改变 (i,j) 的限制关系。于是我们做一些放缩的处理。首先把向下取整的符号拿掉

    [j<leftlfloor frac{ai+b}{c} ight floor Leftrightarrow j+1leq leftlfloor frac{ai+b}{c} ight floor Leftrightarrow j+1leq frac{ai+b}{c}\ ]

    然后可以做一些变换

    [j+1leq frac{ai+b}{c} Leftrightarrow jc+cle ai+b Leftrightarrow jc+c-b-1< ai ]

    最后一步,向下取整得到:

    [jc+c-b-1< aiLeftrightarrow leftlfloorfrac{jc+c-b-1}{a} ight floor< i ]

    这一步的重要意义在于,我们可以把变量 (i) 消掉了!具体地,令 (m=leftlfloor frac{an+b}{c} ight floor),那么原式化为

    [egin{split} f(a,b,c,n)&=sum_{j=0}^{m-1} sum_{i=0}^nleft[i>leftlfloorfrac{jc+c-b-1}{a} ight floor ight]\ &=sum_{j=0}^{m-1} n-leftlfloorfrac{jc+c-b-1}{a} ight floor\ &=nm-fleft(c,c-b-1,a,m-1 ight) end{split} ]

    这是一个递归的式子。并且你发现 (a,c) 分子分母换了位置,又可以重复上述过程。先取模,再递归。这就是一个辗转相除的过程,这也是类欧几里德算法的得名。

    容易发现时间复杂度为 (mathcal{O}(lg(n + m + a + b)))

    同时关于 类欧几里德算法 有两个函数的拓展

    扩展

    理解了最基础的类欧几里德算法,我们再来思考以下两个变种求和式:

    [g(a,b,c,n)=sum_{i=0}^nileftlfloor frac{ai+b}{c} ight floor\ h(a,b,c,n)=sum_{i=0}^nleftlfloor frac{ai+b}{c} ight floor^2 ]

    推导 g

    我们先考虑 (g),类似地,首先取模:

    [g(a,b,c,n) =g(amod c,bmod c,c,n)+leftlfloorfrac{a}{c} ight floorfrac{n(n+1)(2n+1)}{6}+leftlfloorfrac{b}{c} ight floorfrac{n(n+1)}{2} ]

    接下来考虑 (a<c,b<c) 的情况,令 (m=leftlfloorfrac{an+b}{c} ight floor)。之后的过程我会写得很简略,因为方法和上文略同:

    [egin{split} &g(a,b,c,n)=sum_{i=0}^nileftlfloor frac{ai+b}{c} ight floor\ &=sum_{j=0}^{m-1} sum_{i=0}^nleft[j<leftlfloorfrac{ai+b}{c} ight floor ight]cdot i end{split} ]

    这时我们设 (t=leftlfloorfrac{jc+c-b-1}{a} ight floor),可以得到

    [egin{split} &=sum_{j=0}^{m-1}sum_{i=0}^n[i>t]cdot i\ &=sum_{j=0}^{m-1}frac{1}{2}(t+n+1)(n-t)\ &=frac{1}{2}left[mn(n+1)-sum_{j=0}^{m-1}t^2-sum_{j=0}^{m-1}t ight]\ &=frac{1}{2}[mn(n+1)-h(c,c-b-1,a,m-1)-f(c,c-b-1,a,m-1)] end{split} ]

    推导 h

    同样的,首先取模:

    [egin{split} h(a,b,c,n)&=h(amod c,bmod c,c,n)\ &+2leftlfloorfrac{b}{c} ight floor f(amod c,bmod c,c,n) +2leftlfloorfrac{a}{c} ight floor g(amod c,bmod c,c,n)\ &+leftlfloorfrac{a}{c} ight floor^2frac{n(n+1)(2n+1)}{6}+leftlfloorfrac{b}{c} ight floor^2(n+1) +leftlfloorfrac{a}{c} ight floorleftlfloorfrac{b}{c} ight floor n(n+1) end{split} ]

    考虑 (a<c,b<c) 的情况,(m=leftlfloordfrac{an+b}{c} ight floor, t=leftlfloordfrac{jc+c-b-1}{a} ight floor).

    我们发现这个平方不太好处理,于是可以这样把它拆成两部分:

    [n^2=2dfrac{n(n+1)}{2}-n=left(2sum_{i=0}^ni ight)-n ]

    这样做的意义在于,添加变量 (j) 的时侯就只会变成一个求和算子,不会出现 (sum imes sum) 的形式:

    [egin{split} &h(a,b,c,n)=sum_{i=0}^nleftlfloor frac{ai+b}{c} ight floor^2 =sum_{i=0}^nleft[left(2sum_{j=1}^{leftlfloor frac{ai+b}{c} ight floor}j ight)-leftlfloorfrac{ai+b}{c} ight floor ight]\ =&left(2sum_{i=0}^nsum_{j=1}^{leftlfloor frac{ai+b}{c} ight floor}j ight) -f(a,b,c,n)\ end{split} ]

    接下来考虑化简前一部分:

    [egin{split} &sum_{i=0}^nsum_{j=1}^{leftlfloor frac{ai+b}{c} ight floor}j\ =&sum_{i=0}^nsum_{j=0}^{leftlfloor frac{ai+b}{c} ight floor-1}(j+1)\ =&sum_{j=0}^{m-1}(j+1) sum_{i=0}^nleft[j<leftlfloor frac{ai+b}{c} ight floor ight]\ =&sum_{j=0}^{m-1}(j+1)sum_{i=0}^n[i>t]\ =&sum_{j=0}^{m-1}(j+1)(n-t)\ =&frac{1}{2}nm(m+1)-sum_{j=0}^{m-1}(j+1)leftlfloor frac{jc+c-b-1}{a} ight floor\ =&frac{1}{2}nm(m+1)-g(c,c-b-1,a,m-1)-f(c,c-b-1,a,m-1) end{split} ]

    因此

    [h(a,b,c,n)=nm(m+1)-2g(c,c-b-1,a,m-1)-2f(c,c-b-1,a,m-1)-f(a,b,c,n) ]

    在代码实现的时侯,因为 (3) 个函数各有交错递归,因此可以考虑三个一起整体递归,同步计算,否则有很多项会被多次计算。这样实现的复杂度是 (O(log n)) 的。

    模板代码实现
    
    
    #define int long long
    using namespace std;
    const int P = 998244353;
    int i2 = 499122177, i6 = 166374059;
    struct data {
        data() { f = g = h = 0; }
        int f, g, h;
    };  // 三个函数打包
    data calc(int n, int a, int b, int c) {
        int ac = a / c, bc = b / c, m = (a * n + b) / c, n1 = n + 1,
            n21 = n * 2 + 1;
        data d;
        if (a == 0) {  // 迭代到最底层
            d.f = bc * n1 % P;
            d.g = bc * n % P * n1 % P * i2 % P;
            d.h = bc * bc % P * n1 % P;
            return d;
        }
        if (a >= c || b >= c) {  // 取模
            d.f = n * n1 % P * i2 % P * ac % P + bc * n1 % P;
            d.g = ac * n % P * n1 % P * n21 % P * i6 % P +
                  bc * n % P * n1 % P * i2 % P;
            d.h = ac * ac % P * n % P * n1 % P * n21 % P * i6 % P +
                  bc * bc % P * n1 % P + ac * bc % P * n % P * n1 % P;
            d.f %= P, d.g %= P, d.h %= P;
    
            data e = calc(n, a % c, b % c, c);  // 迭代
    
            d.h += e.h + 2 * bc % P * e.f % P + 2 * ac % P * e.g % P;
            d.g += e.g, d.f += e.f;
            d.f %= P, d.g %= P, d.h %= P;
            return d;
        }
        data e = calc(m - 1, c, c - b - 1, a);
        d.f = n * m % P - e.f, d.f = (d.f % P + P) % P;
        d.g = m * n % P * n1 % P - e.h - e.f, d.g = (d.g * i2 % P + P) % P;
        d.h = n * m % P * (m + 1) % P - 2 * e.g - 2 * e.f - d.f;
        d.h = (d.h % P + P) % P;
        return d;
    }
    int T, n, a, b, c;
    signed main() {
        scanf("%lld", &T);
        while (T--) {
            scanf("%lld%lld%lld%lld", &n, &a, &b, &c);
            data ans = calc(n, a, b, c);
            printf("%lld %lld %lld
    ", ans.f, ans.h, ans.g);
        }
        return 0;
    }
    


    The desire of his soul is the prophecy of his fate
    你灵魂的欲望,是你命运的先知。

  • 相关阅读:
    图像检索(image retrieval)- 11
    图像检索(image retrieval)- 10相关
    Mock.js简易教程,脱离后端独立开发,实现增删改查功能
    Azure Monitor (3) 对虚拟机磁盘设置自定义监控
    Azure Monitor (1) 概述
    Azure SQL Managed Instance (2) 备份SQL MI
    Azure Virtual Network (17) Private Link演示
    Azure Virtual Network (16) Private Link
    Azure Virtual Network (15) Service Endpoint演示
    Azure Virtual Network (14) Service Endpoint服务终结点
  • 原文地址:https://www.cnblogs.com/RioTian/p/14584189.html
Copyright © 2011-2022 走看看