类欧几里得算法学习笔记

类欧几里得算法 是 欧几里得算法 的拓展.

这里介绍万能欧几里得算法, 他适用性广泛, 实现简单, 相信你一下就能学会.

模型

万能欧几里得算法的使用场景为:

在一个平面直角坐标系中, 有一条直线 y = p x + r q y=\dfrac {px+r}q y=qpx+r ,当其碰到一条横线时 执行 U U U 操作, 碰到一条竖线时执行 R R R 操作.(特别的, 当同时碰到定义为先执行 U U U).

操作必须满足结合律, 交换律不需要有.

我们定义两个操作 s , t s,t s,t 的结合为 s + t s+t s+t 或者 s ⋅ t s \cdot t st .

然后你需要回答操作序列完成后的答案.
类欧几里得算法学习笔记_第1张图片

比如这条直线在 ( 0 , 5 ] (0,5] (0,5] 的定义域下, 执行的操作为 R R U    R R U R RRU ~~RRUR RRU  RRUR

解决方法

s o l v e ( p , q , r , l , a , b ) solve(p, q, r,l, a, b) solve(p,q,r,l,a,b) 表示 y = p x + r q y = \dfrac{px + r}q y=qpx+r ( 0 , l ] (0,l] (0,l] 内, U U U操作为 a a a, R R R 操作为 b b b 的总操作情况.

p ≥ q p \ge q pq, 一次 R R R 操作前面一定有 ⌊ p q ⌋ \lfloor \dfrac p q\rfloor qp U U U 操作, 所以我们可以给他们绑定一下, 此时 r e t u r n   s o l v e ( p m o d    q , q , r , l , a , a ⌊ p q ⌋ b ) return ~solve(p \mod q,q,r,l,a, a^{\lfloor \frac p q\rfloor } b) return solve(pmodq,q,r,l,a,aqpb).

否则, 我们考虑给坐标轴来个对 $x = y $ 轴对称.

我们先来考虑一个简单的问题, 第 a a a R R R 前面有多少个 U U U.

显然, 我们直接算他的 y y y 坐标即可, 所以有 ⌊ p a + r q ⌋ \lfloor \dfrac {pa + r}q\rfloor qpa+r.

然后考量一下, 第 a a a U U U 前面有多少个 R R R.

设第 b b b R R R 在其前面, 则有
⌊ p b + r q ⌋ < a p b + r < a q p b ≤ a q − r − 1 b ≤ ⌊ a q − r − 1 p ⌋ \lfloor \dfrac {pb + r}q\rfloor < a\\ pb + rqpb+r<apb+r<aqpbaqr1bpaqr1
所以有 ⌊ a q − r − 1 p ⌋ \lfloor \dfrac {aq-r-1}p\rfloor paqr1 这么多个.

现在问题就好解决了!!

大体上就是 重定义直线为 y = q x − r − 1 p y = \dfrac {qx-r-1}p y=pqxr1 , 然后每碰到一次 横线执行 R R R, 碰到竖线执行 U U U.

然后边界情况我们暴力处理:

  1. 第一个 U U U 及其前面的操作.
  2. 最后一个 U U U 之后的 R R R 操作.

系数自己推一下就行啦~~

node solve(ll p, ll q, ll r, ll l, node a, node b) {
    if (!l) //定义域为空
        return node();
    if (p >= q)
        return solve(p % q, q, r, l, a, power(a, p / q) + b);
    ll m = (l * p + r) / q; //有多少个 U
    if (!m) //只有 R操作了
        return power(b, l);
    return power(b, (q - r - 1) / p) + a + solve(q, p, (q - r - 1) % p, m - 1, b, a)
        + power(b, l - (q * m - r - 1) / p);
}

乍一看这个算法大概是 O ( log ⁡ 2 max ⁡ ( p , q ) ) O(\log ^2 \max (p,q)) O(log2max(p,q)) 的(当合并操作为 O ( 1 ) O(1) O(1) )

实际上不然.

可以发现每一层的复杂度为 log ⁡ max ⁡ ( p , q ) − log ⁡ min ⁡ ( p , q ) \log \max(p,q) - \log \min(p, q) logmax(p,q)logmin(p,q) 所以总复杂度应该为 O ( log ⁡ max ⁡ ( p , q ) ) O(\log \max(p,q)) O(logmax(p,q)).

例题

P5171 Earthquake

简单的模板题.(并没有用到万能欧几里得算法)

n = c / a n = c/a n=c/a, 则 a n s = ∑ x = 0 n ⌊ 1 + a x + c m o d    a b ⌋ ans = \sum_{x=0}^n \lfloor 1+\dfrac {ax + c \mod a}b\rfloor ans=x=0n1+bax+cmoda

f ( p , q , r , n ) = ∑ x = 0 n ⌊ p x + r q ⌋ f(p,q,r,n)=\sum_{x=0}^n \lfloor \dfrac {px +r }q\rfloor f(p,q,r,n)=x=0nqpx+r.

如果 p ≥ q ∪ r ≥ q p\ge q \cup r\ge q pqrq, 我们可以先把部分确定的值( ⌊ r q ⌋ . . \lfloor \dfrac r q\rfloor.. qr..)给提出来.

剩下情况皆为 p < q ∩ r < q p < q \cap r < q p<qr<q, 我们大力推式子:
{ f ( p , q , r , n ) = ∑ x = 0 n ⌊ p x + r q ⌋ = ∑ x = 0 n ∑ y = 0 y < ⌊ p x + r q ⌋ ( m = ( p n + r ) / q ) = ∑ y = 0 m − 1 n − ⌊ q y + q − r − 1 p ⌋ = n m − f ( q , p , q − r − 1 , m − 1 ) \begin{cases}f(p,q,r,n)&=\sum_{x=0}^n \lfloor \dfrac {px +r }q\rfloor\\ &=\sum_{x=0}^n \sum_{y=0}y < \lfloor \dfrac {px +r }q\rfloor \\ & (m = (pn+r)/q) \\ &= \sum_{y=0}^{m - 1} n- \lfloor \dfrac {qy + q - r - 1}p\rfloor\\ &= nm - f(q, p, q - r-1, m-1) \end{cases} f(p,q,r,n)=x=0nqpx+r=x=0ny=0y<qpx+r(m=(pn+r)/q)=y=0m1npqy+qr1=nmf(q,p,qr1,m1)

ll f(ll p, ll q, ll r, ll n) {
    if (!p)
        return 0;
    if (p >= q || r >= q)
        return p / q * (n + 1) * n / 2 + r / q * (n + 1) + f(p % q, q, r % q, n);
    ll m = (p * n + r) / q;
    return n * m - f(q, p, q - r - 1, m - 1);
}

void solve() {
    ll p, q, r, n;
    qr(p, q, r);
    n = r / p;
    pr2(n + 1 + f(p, q, r % p, n));
}

P5170 【模板】类欧几里得算法

这个我们维护一下 x , y , s u m x , s u m y , s q u a r e y , p r o d   o f   x , y x,y, sumx,sumy,squarey, prod~of ~x,y x,y,sumx,sumy,squarey,prod of x,y 即可.

TPP void ad(t1& x, t2 y) {
    x += y;
    if (x >= mod)
        x -= mod;
}
TPP void dl(t1& x, t2 y) {
    x -= y;
    if (x < 0)
        x += mod;
}

struct node {
    ll x, y, sx, sy, ss, p;
    node() { x = y = sx = sy = ss = p = 0; }
    node operator+(node b) {
        node c = *this;
        ad(c.x, b.x);
        ad(c.y, b.y);
        ad(c.sx, b.sx);
        ad(c.sx, x * b.x % mod);
        ad(c.sy, b.sy);
        ad(c.sy, y * b.x % mod);
        ad(c.ss, (b.ss + y * y % mod * b.x + 2 * y * b.sy) % mod);
        ad(c.p, (b.p + b.sx * y + b.sy * x + x * y % mod * b.x) % mod);
        return c;
    }
} U, R, ans;

node power(node a, int b) {
    node c;
    while (b) {
        if (b & 1)
            c = c + a;
        b /= 2;
        a = a + a;
    }
    return c;
}

node solve(ll p, ll q, ll r, ll l, node a, node b) {
    if (!l)
        return node();
    if (p >= q)
        return solve(p % q, q, r, l, a, power(a, p / q) + b);
    ll m = (l * p + r) / q;
    if (!m)
        return power(b, l);
    return power(b, (q - r - 1) / p) + a + solve(q, p, (q - r - 1) % p, m - 1, b, a)
        + power(b, l - (q * m - r - 1) / p);
}

void solve() {
    ll l, p, q, r;
    qr(l, p, r, q);
    ans = power(U, r / q) + solve(p, q, r % q, l, U, R);
    r /= q;
    pr1((ans.sy + r) % mod, (ans.ss + r * r) % mod, ans.p);
    puts("");
}

int main() {
    U.y = 1;
    R.x = R.sx = 1;
    int T = 1;
    qr(T);
    while (T--)
        solve();
    return 0;
}

bzoj #3817. Sum

类欧几里得算法学习笔记_第2张图片

如果 R R R 为完全平方数就比较平凡.

我们着重讨论另一部分!

首先做一个转换:
( − 1 ) x = ⌊ x 2 ⌋ ∗ 4 − ⌊ x ⌋ ∗ 2 + 1 A n s = ∑ x = 1 n ( − 1 ) ⌊ d R ⌋ = ∑ x = 1 n ⌊ d R 2 ⌋ ∗ 4 − ⌊ d R ⌋ ∗ 2 + 1 (-1)^x = \lfloor \dfrac x 2\rfloor * 4 - \lfloor x \rfloor * 2 + 1 \\ Ans=\sum_{x=1}^n (-1)^{\lfloor d\sqrt R\rfloor} \\ =\sum_{x=1}^n \lfloor \dfrac {d\sqrt R} 2\rfloor * 4 - \lfloor d\sqrt R \rfloor * 2 + 1 (1)x=2x4x2+1Ans=x=1n(1)dR =x=1n2dR 4dR 2+1
所以, 现在的问题是求一个斜率带 R \sqrt R R 的直线下的整点数.

这个题比较卡精度, 如果直接用 l o n g   d o u b l e long ~double long double 会错(然而并没有实现过, qaq, 这是听别人讲的

我们考虑把斜率扩一下域, 设 t = R , p t + r q t = \sqrt R, \dfrac {pt + r}q t=R ,qpt+r 为斜率.

我们来考虑一个子问题:
f ( n , p , q , r ) = ∑ i = 1 n ⌊ p t + r q ⋅ i ⌋ f(n, p, q,r)=\sum_{i=1}^n \lfloor \dfrac {pt + r}q\cdot i \rfloor f(n,p,q,r)=i=1nqpt+ri
我们可以先把 f l o o r ( p t + R q ) floor(\dfrac {pt + R}q) floor(qpt+R) 这一部分求答案, 然后 R R R 修改一下.

此时, 我们把坐标轴翻转一下, 由于斜率带 t t t 所以这条直线没有经过一个整点.(除了原点)

m m m 为定义域下最大的 y y y 坐标.
f ( n , p , q , r ) = ∑ i = 1 n ⌊ p t + r q ⋅ i ⌋ = = ∑ i = 1 m n − ⌊ q p t + r ⋅ i ⌋ = n m − ∑ i = 1 m ⌊ q p t + r ⋅ i ⌋ = n m − ∑ i = 1 m ⌊ q ∗ ( p t − r ) ( p t + r ) ∗ ( p t − r ) ⋅ i ⌋ = n m − ∑ i = 1 m ⌊ q p t − q r ) p 2 R − r 2 ⋅ i ⌋ = n m − f ( m , q p , p 2 R − r 2 , − q r ) f(n, p, q,r)=\sum_{i=1}^n \lfloor \dfrac {pt + r}q\cdot i \rfloor=\\ =\sum_{i=1}^m n - \lfloor \dfrac q{pt + r}\cdot i \rfloor \\ =nm - \sum_{i=1}^m \lfloor \dfrac q{pt + r}\cdot i \rfloor \\ =nm - \sum_{i=1}^m \lfloor \dfrac {q*(pt -r)}{(pt + r)*(pt -r)}\cdot i \rfloor \\ =nm - \sum_{i=1}^m \lfloor \dfrac {qpt -qr)}{p^2R - r^2}\cdot i \rfloor \\ =nm - f(m, qp,p^2R-r^2, -qr) f(n,p,q,r)=i=1nqpt+ri==i=1mnpt+rqi=nmi=1mpt+rqi=nmi=1m(pt+r)(ptr)q(ptr)i=nmi=1mp2Rr2qptqr)i=nmf(m,qp,p2Rr2,qr)
这个递归的边界为 n = 0 n=0 n=0.

然后, 我们来考虑一下递归的层数如何, 因为这就是复杂度.

k k k 为斜率, 那么每次相当于 k % = 1 , n ∗ = k k\%=1,n*=k k%=1,n=k.

k ≤ 1 / 2 k\le 1/2 k1/2, 那么显然 m ≤ n / 2 m\le n/2 mn/2.

否则, 1 / k ∈ ( 1 , 2 ) 1/k \in (1,2) 1/k(1,2), 那么两次递归导致 n n n 的变化率为 k ( 1 / k − 1 ) = 1 − k < 1 / 2 k(1/k - 1)=1-k < 1/2 k(1/k1)=1k<1/2.

所以每减小一半需要 O ( 1 ) O(1) O(1) 次递归, 显然复杂度为 O ( log ⁡ n ) O(\log n) O(logn).

TIP: 每次 p , q , r p,q,r p,q,r 要除一下 g c d gcd gcd 防止溢出. (至于为啥 l o n g   l o n g long ~long long long 存得下,我不会分析, 有谁可以教教我吗/kel)

ll n, m;
double t;

ll f(ll n, ll p, ll q, ll r) { // ! sum i in [1, n] floor( (pt + r) / q * i )
    if (!n)
        return 0;

    ll y, ans;
    y = __gcd(p, __gcd(q, r));
    p /= y;
    q /= y;
    r /= y;
    y = (p * t + r) / q, ans = (n + 1) * n / 2 * y;
    r -= q * y;
    y = (p * t + r) * n / q;
    return ans + n * y - f(y, p * q, p * p * m - r * r, -q * r);
}

void solve() {
    qr(n, m);
    t = sqrt(m);
    if ((int)t * t == m)
        pr2(m & 1 ? (n & 1 ? -1 : 0) : n);
    else
        pr2(n + 4 * f(n, 1, 2, 0) - 2 * f(n, 1, 1, 0));
}

#6440. 万能欧几里得

类欧几里得算法学习笔记_第3张图片

这里套用万能欧几里得算法:

n o d e node node 中记录 x , y , A x , B y , p r o d A B x,y, A^x, B^y,prod_{AB} x,y,Ax,By,prodAB.

然后就比较平凡了.

TIP:用long double 防止溢出 l o n g   l o n g long ~long long long.

ll p, q, r, l;
int n;

struct rec {
    int a[N][N];
    rec(int v = 0) {
        memset(a, 0, sizeof a);
        if (v)
            FOR(i, n) a[i][i] = v;
    }
    rec operator*(const rec b) const {
        rec c;
        FOR(i, n) FOR(j, n) FOR(k, n) ad(c.a[i][k], 1LL * a[i][j] * b.a[j][k] % mod);
        return c;
    }
    void operator+=(const rec b) { FOR(i, n) FOR(j, n) ad(a[i][j], b.a[i][j]); }
    void init() { FOR(i, n) FOR(j, n) qr(a[i][j]); }
};

struct node {
    ll x, y;
    rec A, B, S;
    node() {
        x = y = 0;
        A = B = rec(1);
        S = rec();
    }
    node operator+(const node& b) {
        node c = *this;
        c.S += A * b.S * B;
        c.A = c.A * b.A;
        c.B = c.B * b.B;
        ad(c.x, b.x);
        ad(c.y, b.y);
        return c;
    }
    node operator^(ll b) {
        node c, a = *this;
        while (b) {
            if (b & 1)
                c = c + a;
            b /= 2;
            a = a + a;
        }
        return c;
    }
};

node f(ll p, ll q, ll r, ll n, node a, node b) {
    if (!n)
        return node();
    if (p >= q)
        return f(p % q, q, r, n, a, (a ^ p / q) + b);
    ll m = ((long double)p * n + r) / q;
    if (!m)
        return b ^ n;
    return (b ^ (q - r - 1) / p) + a + f(q, p, (q - r - 1) % p, m - 1, b, a)
        + (b ^ n - ((ll)(((long double)q * m - r - 1) / p)));
}

void solve() {
    qr(p, q, r, l, n);
    rec A, B;
    node U, R, ans;
    A.init();
    B.init();
    U.y = 1;
    U.B = B;
    R.x = 1;
    R.A = R.S = A;
    ans = (U ^ r / q) + f(p, q, r % q, l, U, R);
    // int last = 0, v;
    // FOR(i, l) {
    //     v = (p * i + r) / q;
    //     if (v ^ last)
    //         ans = ans + (U ^ v - last), last = v;
    //     ans = ans + R;
    //     FOR(x, n) {
    //         FOR(j, n) pr1(ans.S.a[x][j]);
    //         puts("");
    //     }
    // }
    FOR(i, n) {
        FOR(j, n) pr1(ans.S.a[i][j]);
        puts("");
    }
}

LOJ #138. 类欧几里得算法

又是板子qaq.

这个博客 写得挺好的.

前置知识: 拉格朗日插值.

#include 
#define gc getchar()
#define mk make_pair
#define TP template <class o>
#define TPP template <typename t1, typename t2>
#define rep(i, a, b) for (int i = a; i <= b; i++)
#define REP(i, a, b) for (int i = b; i >= a; i--)
#define FOR(i, n) rep(i, 1, n)
using namespace std;
typedef long long ll;
const int N = 13, mod = (int)1e9 + 7;
TP void qr(o& x) {
    char c = gc;
    x = 0;
    int f = 1;
    while (!isdigit(c)) {
        if (c == '-')
            f = -1;
        c = gc;
    }
    while (isdigit(c))
        x = x * 10 + c - '0', c = gc;
    x *= f;
}
template <class o, class... O> void qr(o& x, O&... y) {
    qr(x);
    qr(y...);
}
TP void qw(o x) {
    if (x / 10)
        qw(x / 10);
    putchar(x % 10 + '0');
}
TP void pr2(o x) {
    if (x < 0)
        x = -x, putchar('-');
    qw(x);
    putchar(10);
}

TPP void ad(t1& x, t2 y) {
    x += y;
    if (x >= mod)
        x -= mod;
}
TPP void dl(t1& x, t2 y) {
    x -= y;
    if (x < 0)
        x += mod;
}

struct arr {
    ll a[N];
    int n;
    arr(int _n = 0)
        : n(_n) {
        memset(a, 0, sizeof a);
    }
    inline ll& operator[](int x) { return a[x]; }
    void operator+=(arr b) { rep(i, 0, n) ad(a[i], b[i]); }
    arr operator*(arr b) {
        arr c;
        c.n = n + b.n;
        rep(i, 0, n) rep(j, 0, b.n) ad(c[i + j], a[i] * b[j] % mod);
        return c;
    }
    ll operator()(ll x) {
        ll ans = a[n];
        REP(i, 0, n - 1) ans = (ans * x + a[i]) % mod;
        return ans;
    }
} A[N];
ll C[N][N], F[66][N][N];

ll power(ll a, ll b = mod - 2, ll p = mod) {
    ll c = 1;
    while (b) {
        if (b & 1)
            c = c * a % p;
        b /= 2;
        a = a * a % p;
    }
    return c;
}

void init() {
    int n = 10;
    rep(i, 0, n) {
        C[i][0] = 1;
        FOR(j, i) C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
    }
    rep(i, 0, n) {
        ll s = 0;
        arr x(i + 1), tmp(1);
        rep(j, 0, i + 1) {
            ad(s, power(j, i));
            arr y;
            y[0] = 1;
            rep(k, 0, i + 1) if (j ^ k) y[0] = y[0] * (j - k) % mod;
            y[0] = (power(y[0]) + mod) * s % mod;
            rep(k, 0, i + 1) if (j ^ k) {
                tmp[0] = mod - k;
                tmp[1] = 1;
                y = y * tmp;
            }
            x += y;
        }
        A[i] = x;
    }
}

ll f(ll p, ll q, ll r, ll n, int k1, int k2, int d) {
    ll& ans = F[d][k1][k2];
    if (~ans)
        return ans;
    ll m = (p * n + r) / q;
    if (!n)
        return ans = k1 ? 0LL : power(m, k2);
    ans = A[k1](n) * power(m, k2) % mod;
    if (!p || !k2)
        return ans;
    if (p >= q || r >= q) {
        ll u = p / q, v = r / q;
        p %= q;
        r %= q;
        ans = 0;
        for (int i = 0, I = 1; i <= k2 && I; i++, I = I * u % mod)
            for (int j = 0, J = I; i + j <= k2 && J; j++, J = J * v % mod)
                ad(ans, C[k2][i] * C[k2 - i][j] * f(p, q, r, n, k1 + i, k2 - i - j, d + 1) % mod * J % mod);
    } else {
        --m;
        rep(k, 0, k2 - 1) rep(t, 0, k1 + 1)
            dl(ans, C[k2][k] * A[k1].a[t] % mod * f(q, p, q - r - 1, m, k, t, d + 1) % mod);
    }
    return ans;
}

void solve() {
    ll p, q, r, n, k1, k2;
    qr(n, p, r, q, k1, k2);
    memset(F, -1, sizeof F);
    pr2(f(p, q, r, n, k1, k2, 0));
}

int main() {
    init();
    int T = 1;
    qr(T);
    while (T--)
        solve();
    return 0;
}

总结

看到一条直线 + 下取整应该能直接想到用类欧.

然后, 维护好像也不是特别困难…

重要的是前面的大量转化…

对了, 好像有一个trick没讲:

对于 f ( 1 ) ⊕ f ( 2 ) ⊕ . . . . ⊕ f ( n ) f(1)\oplus f(2)\oplus....\oplus f(n) f(1)f(2)....f(n) 的值.

我们考虑拆位, 然后求 m o d    2 \mod 2 mod2 下的总和即可.(大家应该都会吧)

鸣谢

  • https://www.luogu.com.cn/blog/ix-35/solution-p5170
  • http://old.orzsiyuan.com/articles/algorithm-Similar-Euclidean-Algorithm/
  • https://blog.csdn.net/VictoryCzt/article/details/86099938

你可能感兴趣的:(数论,算法,学习)