类欧几里得算法

我还是觉得我学这玩意纯属闲着没事干。

类欧几里得算法

定义

\[f(a,b,c,n)=\sum_{i=0}^n\left\lfloor\frac{ai+b}c\right\rfloor \]

让你 \(O(\log n)\) 计算这个东西。

首先假如 \(a\ge c\)\(b\ge c\) 我们显然可以把它们对 \(c\) 取模后加上什么东西。推一下式子。

\(a'=a\bmod c,b'=b\bmod c\)

\[\begin{aligned} f(a,b,c,n)=&\sum_{i=0}^n\left\lfloor\frac{ai+b}c\right\rfloor\\ =&\sum_{i=0}^n\left\lfloor\frac{(\lfloor\frac ac\rfloor c+a')i+\lfloor\frac bc\rfloor c+b'}c\right\rfloor\\ =&\sum_{i=0}^n\left\lfloor\frac ac\right\rfloor i+\left\lfloor\frac bc\right\rfloor+\left\lfloor\frac{a'i+b'}{c}\right\rfloor\\ =&\left\lfloor\frac ac\right\rfloor\frac{n(n+1)}2+\left\lfloor\frac bc\right\rfloor(n+1)+f(a',b',c,n) \end{aligned} \]

然后考虑 \(a<c,b<c\) 的情况怎么搞。以下是这类长的像类欧的东西怎么推的大概套路。

\[\begin{aligned} f(a,b,c,n)=&\sum_{i=0}^n\left\lfloor\frac{ai+b}c\right\rfloor\\ =&\sum_{i=0}^n\sum_{j=0}^{\left\lfloor\frac{ai+b}c\right\rfloor-1}1\\ =&\sum_{j=0}^{\lfloor\frac{an+b}c\rfloor-1}\sum_{i=0}^n\left[j<\left\lfloor\frac{ai+b}c\right\rfloor\right] \end{aligned} \]

把后边的拆一下:

\[\begin{aligned} &\left[j<\left\lfloor\frac{ai+b}c\right\rfloor\right]\\ =&\left[j+1\le\left\lfloor\frac{ai+b}c\right\rfloor\right]\\ =&\left[j+1\le\frac{ai+b}c\right]\\ =&[jc+c\le ai+b]\\ =&[jc+c-b-1<ai]\\ =&\left[i>\left\lfloor\dfrac{jc+c-b-1}a\right\rfloor\right] \end{aligned} \]

代入原式:

\[\begin{aligned} &\sum_{j=0}^{\lfloor\frac{an+b}c\rfloor-1}\sum_{i=0}^n\left[i>\left\lfloor\dfrac{jc+c-b-1}a\right\rfloor\right]\\ =&\sum_{j=0}^{\lfloor\frac{an+b}c\rfloor-1}\left(n-\left\lfloor\dfrac{jc+c-b-1}a\right\rfloor\right)\\ =&n\left\lfloor\frac{an+b}c\right\rfloor-f(c,c-b-1,a,\left\lfloor\frac{an+b}c\right\rfloor-1) \end{aligned} \]

发现这里分母 \(c\) 和分子 \(a\) 互换,可以继续取模向下递归。终止条件当 \(a=0\)\(f(a,b,c,n)=n\left\lfloor\dfrac bc\right\rfloor\)

int f(int a,int b,int c,int n){
    if(!a)return b/c*(n+1);
    if(a>=c||b>=c)return n*(n+1)/2*(a/c)+(n+1)*(b/c)+f(a%c,b%c,c,n);
    int m=(a*n+b)/c;
    return n*m-f(c,c-b-1,a,m-1);
}

然后继续推板子的另外两个式子:

\[g(a,b,c,n)=\sum_{i=0}^ni\left\lfloor\dfrac{ai+b}c\right\rfloor \]

\[h(a,b,c,n)=\sum_{i=0}^n\left\lfloor\dfrac{ai+b}c\right\rfloor^2 \]

g

先看 \(a,b\)\(c\) 取模后的变化。以下均设 \(a'=a\bmod c,b'=b\bmod c,m=\left\lfloor\dfrac{an+b}c\right\rfloor,t=\left\lfloor\dfrac{jc+c-b-1}a\right\rfloor\)

\[\begin{aligned} g(a,b,c,n)=&\sum_{i=0}^ni\left\lfloor\frac{ai+b}c\right\rfloor\\ =&\sum_{i=0}^ni\left\lfloor\frac{(\lfloor\frac ac\rfloor c+a')i+\lfloor\frac bc\rfloor c+b'}c\right\rfloor\\ =&\sum_{i=0}^n\left\lfloor\frac ac\right\rfloor i^2+\left\lfloor\frac bc\right\rfloor i+\left\lfloor\frac{a'i+b'}c\right\rfloor\\ =&\left\lfloor\frac ac\right\rfloor\frac{n(n+1)(2n+1)}6+\left\lfloor\frac bc\right\rfloor\frac{n(n+1)}2+g(a',b',c,n) \end{aligned} \]

\[\begin{aligned} g(a,b,c,n)=&\sum_{i=0}^ni\left\lfloor\frac{ai+b}c\right\rfloor\\ =&\sum_{i=0}^ni\sum_{j=0}^{\lfloor\frac{ai+b}c\rfloor}1\\ =&\sum_{j=0}^{m-1}\sum_{i=0}^ni\left[i<\left\lfloor\frac{ai+b}c\right\rfloor\right]\\ =&\sum_{j=0}^{m-1}\sum_{i=0}^ni[i>t]\\ =&\sum_{j=0}^{m-1}\frac{(n+t+1)(n-t)}2\\ =&\frac 12\left(mn(n+1)-h(c,c-b-1,a,m-1)-f(c,c-b-1,a,m-1)\right) \end{aligned} \]

h

\[\begin{aligned} h(a,b,c,n)=&\sum_{i=0}^n\left\lfloor\frac{ai+b}c\right\rfloor^2\\ =&\sum_{i=0}^n\left\lfloor\frac{(\lfloor\frac ac\rfloor c+a')i+\lfloor\frac bc\rfloor c+b'}c\right\rfloor^2\\ =&\sum_{i=0}^n\left(\left\lfloor\frac ac\right\rfloor i+\left\lfloor\frac bc\right\rfloor +\left\lfloor\frac{a'i+b'}c\right\rfloor\right)^2\\ =&\sum_{i=0}^n\left(\left\lfloor\frac ac\right\rfloor i\right)^2+\left\lfloor\frac bc\right\rfloor^2+\left\lfloor\frac{a'i+b'}c\right\rfloor^2+2\left\lfloor\frac ac\right\rfloor\left\lfloor\frac bc\right\rfloor i+2\left\lfloor\frac ac\right\rfloor\left\lfloor\frac{a'i+b'}c\right\rfloor+2\left\lfloor\frac bc\right\rfloor\left\lfloor\frac{a'i+b'}c\right\rfloor\\ =&h(a',b',c,n)+2\left\lfloor\frac bc\right\rfloor f(a',b',c,n)+2\left\lfloor\frac ac\right\rfloor g(a',b',c,n)+\\ &\left\lfloor\frac ac\right\rfloor^2\frac{n(n+1)(2n+1)}6+\left\lfloor\frac bc\right\rfloor^2(n+1)+\left\lfloor\frac ac\right\rfloor\left\lfloor\frac bc\right\rfloor n(n+1) \end{aligned} \]

首先可以把 \(n^2\) 化为

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

来避免化简的时候出现两个求和号相乘。

\[\begin{aligned} h(a,b,c,n)=&\sum_{i=0}^n\left\lfloor\frac{ai+b}c\right\rfloor^2\\ =&\sum_{i=0}^n\left(2\sum_{j=0}^{\lfloor\frac{ai+b}c\rfloor}j-\left\lfloor\frac{ai+b}c\right\rfloor\right)\\ =&2\sum_{i=0}^n\sum_{j=1}^{\lfloor\frac{ai+b}c\rfloor}j-f(a,b,c,n) \end{aligned} \]

看前面一半:

\[\begin{aligned} &\sum_{i=0}^n\sum_{j=1}^{\lfloor\frac{ai+b}c\rfloor}j\\ =&\sum_{i=0}^n\sum_{j=0}^{\lfloor\frac{ai+b}c\rfloor-1}j+1\\ =&\sum_{j=0}^{m-1}(j+1)\sum_{i=0}^n\left[j<\left\lfloor\frac{ai+b}c\right\rfloor\right]\\ =&\sum_{j=0}^{m-1}(j+1)(n-t)\\ =&\frac 12nm(m+1)-\sum_{j=0}^{m-1}(j+1)t\\ \end{aligned} \]

所以有

\[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) \]

这三个可以一块算。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define int long long
using namespace std;
const int mod=998244353,inv2=499122177,inv6=166374059;
int n,a,b,c;
struct node{
    int f,g,h;
    node(){f=g=h=0;}
};
node solve(int a,int b,int c,int n){
    node ans;
    int m=(1ll*a*n+b)/c,ac=a/c%mod,bc=b/c%mod;
    if(!a){
        ans.f=1ll*bc*(n+1)%mod;
        ans.g=1ll*n*(n+1)%mod*inv2%mod*bc%mod;
        ans.h=1ll*bc*bc%mod*(n+1)%mod;
        return ans;
    }
    if(a>=c||b>=c){
        ans.f=(1ll*n*(n+1)%mod*inv2%mod*ac%mod+1ll*(n+1)*bc%mod)%mod;
        ans.g=(1ll*n*(n+1)%mod*(2*n+1)%mod*inv6%mod*ac%mod+1ll*n*(n+1)%mod*inv2%mod*bc%mod)%mod;
        ans.h=(1ll*n*(n+1)%mod*(2*n+1)%mod*inv6%mod*ac%mod*ac%mod+
                1ll*(n+1)*bc%mod*bc%mod+
                1ll*n*(n+1)%mod*ac%mod*bc%mod)%mod;
        node d=solve(a%c,b%c,c,n);
        ans.f=(ans.f+d.f)%mod;
        ans.g=(ans.g+d.g)%mod;
        ans.h=(ans.h+d.h+2ll*bc%mod*d.f%mod+2ll*ac%mod*d.g%mod)%mod;
        return ans;
    }
    node d=solve(c,c-b-1,a,m-1);
    ans.f=(1ll*n*m%mod-d.f+mod)%mod;
    ans.g=(1ll*n*(n+1)%mod*m%mod-d.h-d.f+mod+mod)%mod*inv2%mod;
    ans.h=(1ll*n*m%mod*(m+1)%mod-2ll*d.g%mod-2ll*d.f%mod-ans.f+3ll*mod)%mod;
    return ans;
}
signed main(){
    int tim;scanf("%lld",&tim);
    while(tim--){
        scanf("%lld%lld%lld%lld",&n,&a,&b,&c);
        node ans=solve(a,b,c,n);
        printf("%lld %lld %lld\n",ans.f,ans.h,ans.g);
    }
    return 0;
}

下面是几个题。

P5171 Earthquake

大板板题。题意是求这个:

\[\sum_{x=0}^{\lfloor\frac ca\rfloor}\left\lfloor\frac{-ax+c}b\right\rfloor+1 \]

上边挂个负数没法类欧,那假设 \(b>a\) (如果相反可以交换),那么加一个 \(x\) 减一个 \(x\) 有:

\[\sum_{x=0}^{\lfloor\frac ca\rfloor}\left\lfloor\frac{(b-a)x+c}b\right\rfloor-x+1 \]

算就行了。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define int long long
using namespace std;
int n,a,b,c;
int f(int a,int b,int c,int n){
    if(!a)return b/c*(n+1);
    int ans=0;
    if(a>=c||b>=c){
        ans=n*(n+1)/2*(a/c)+(n+1)*(b/c)+f(a%c,b%c,c,n);
        return ans;
    }
    int m=(a*n+b)/c;
    int d=f(c,c-b-1,a,m-1);
    ans=n*m-d;
    return ans;
}
signed main(){
    scanf("%lld%lld%lld",&a,&b,&c);
    if(a>b)swap(a,b);
    printf("%lld\n",f(b-a,c,b,c/a)-(c/a)*(c/a+1)/2+c/a+1);
    return 0;
}

[COCI2009-2010#1] ALADIN

首先这题我没写纯口胡的。因为这题线段树还 tm 要离散化而且恶意卡空间(动态开点容易 MLE,空间 64MB 时间 8s 然而基本最大的点都是 1s 过的)我不知道什么意味。

首先关于这个修改操作,线段树每个节点可以记录一个 \(L,A,B\),这样这个区间的和就可以类欧算。线段树查询就正常查就行了。然而代码不是一般的恶心。复杂度 \(O(q\log^2n)\)

万能欧几里得到时候再说。

posted @ 2023-02-03 17:28  gtm1514  阅读(16)  评论(0编辑  收藏  举报