文章

能力综合提升-数学-2

能力综合提升-数学-2

0x04 裴蜀定理

内容

a,b 不全为 0 时,x,y,gcd(a,b)ax+by,且 x,y,ax+by=gcd(a,b)

证明

对于第一点,gcd(a,b)agcd(a,b)bgcd(a,b)axgcd(a,b)bygcd(a,b)ax+by

对于第二点,记 d=gcd(a,b),s=minax+by>0(ax+by)

a=ps+r,则 r=aps=ap(ax+by)=a(1px)+b(py)

0r<ss 是最小的满足 s=ax+by 的正数,可知 r=0

那么就是 sa,同理有 sb,故 sgcd(a,b)

又由第一点 gcd(a,b)ax+bygcd(a,b)s

那么 s=gcd(a,b)

推论

gcd(a,b)=1x,y,ax+by=1

(a,b) 这种二维的情况可以扩展到 n(a1,a2,,an)

例题

给定互质的正整数 a,b,记 S=dd=ax+by,x0,y0,求 max(NS)。保证一定有解。

0x05 乘法逆元

ax1(modb)xa 在模 b 意义下的逆元,记作 a1

扩展欧几里得算法

1
2
3
4
5
void exgcd(int a, int b, int &x, int &y)
{
    if(b == 0) x = 1, y = 0;
    else exgcd(b, a % b, y, x), y -= a / b * x;
}

用来求解形如 ax+by=gcd(a,b) 的方程的一组解。

b=0 时,gcd(a,b)=a,那么显然有 x=1y 可以是任意数,这里令 y=0 (其实别的也无所谓)。

b0 时,考虑 gcd(a,b)=gcd(b,amodb),先求解 bx+(amodb)y=gcd(b,amodb) 的解 (x0,y0),带入可得

ax+by=bx0+(amodb)y0=bx0+(aabb)y0=ay0+b(x0aby0)

那么原方程的一个解就是 (y0,x0aby0)

要求出所有解,我们显然可以注意到,对 x 加上(或减去)若干个 b 后依然能保证满足方程。若方程的解是 (x,y),那么通解就是 (x+kb,yka),kZ(反证可证没有漏掉其他的解)。

如果我们想求最小的正数 x,可以这么写:x = (x % b + b) % b,它可以处理负数。

回到求逆元问题上来,ax1(modb)ax+by=1,因此只要 a,b 互质 就能使用扩展欧几里得算法求解。和欧几里得算法一样,时间复杂度 O(logp)

1
2
3
4
5
int inv(int a, int b)
{
    int x, y; exgcd(a, b, x, y);
    return (x % b + b) % b;
}

例题

求解 x+kmy+kn(modL)

x+kmy+kn(modL)k(mn)yx(modL)k(mn)+pL=yx

不妨记 u=yx,v=mn,也就是要解不定方程 kv+pL=u

考虑 exgcd 的原理,是用来求解形如 ax+by=gcd(a,b) 的方程的一组解,则

引理:对于不定方程 ax+by=c,记 d=gcd(a,b)

  1. dc 则方程有整数解,且可以通过 exgcd 求出;

  2. dc 则方程无解。

证明

对于第一种情况,考虑 ax+by=d 的方程,由裴蜀定理可知必存在 x0,y0 满足 ax0+by0=d。因此方程的通解是 (x,y)=(x0+bdt,y0adt),tZ。 记 c=kd,则两边同乘 ka(kx)+b(ky)=c,也就找到了特解 (kx,ky)=(kx0+bdkt,ky0adkt)

对于第二种情况,反证,假设存在这样的解使 ax0+by0=c。由于 d=gcd(a,b),可化为 d(adx0+bdy0)=c,即 dc,矛盾。

于是,当 gcd(k,p)u 时,方程无解。否则,先用 exgcd 解出 kv+pL=gcd(k,p) 的解,再乘上倍数即可。

不过需要注意:

  1. yx,mn<0 的情况,最好使用 x = (x % L + L) % L 这样的语句来保证取值非负。

  2. exgcd 求出解后,剩余系变成了 (modLd),对于后面的非负操作要对 L 修改。

  3. #define int ll

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
signed main()
{
    ll x, y, m, n, L;
    read(x, y, m, n, L);
    ll u = ((x - y) % L + L) % L;
    ll v = ((n - m) % L + L) % L;
    ll d = gcd(v, L);
    if(u % d != 0)
        puts("Impossible"), exit(0);
    ll a, b;
    exgcd(v, L, a, b);
    L /= d;
    a = (a % L + L) % L;
    ll ans = a * (u / d) % L;
    write('\n', ans);
    return 0;
}

费马小定理

apa(modp)pP。如果 pa,那么也可以写成 ap11(modp)

注意:费马小定理的逆定理不成立,即不能从 apa(modp) 推出 pP。满足 ama(modm) 的合数 m 被称为 Carmichael 数(参见 Miller-Rabin 内容)。

由于证明方法非常美妙,我打算写三种。

法一:简化剩余系

仅证 pagcd(a,p)=1 的情况。

考虑 S=1,2,3,,p1aS=a,2a,3a,,(p1)a,构造出的 aS 的性质:每个元素仍与 p 互质且在模 p 意义下互不相同。

互质:gcd(a,p)=1 且质数 p 与任何小于它的正数互质。这保证了 modp 后不会出现 0

互不相同:反证,axay(modp)a1axa1ay(modp)xy(modp)

这两个性质保证了 aSSmodp 的一个排列

a×2a×3a××(p1)a1×2×3××(p1)(modp)(p1)!ap1(p1)!(modp)ap11(modp)

证毕。

法二:二项式定理

考虑二项式系数 (pn)=p!n!(pn)!。限定 0<n<p 则分子上的 p 不会被约去,就有 p(pn)

再考虑 (b+1)p 的二项式展开,有

(b+1)p(pp)bp+(pp1)bp1++(p1)b1+(p0)b0(modp)(pp)bp+(p0)b0(modp)bp+1(modp)

(注:这个结论也可以由数学归纳法证明)

因此

ap(a1)p+1(modp)(a2)p+2(modp)(aa)p+a(modp)a(modp)

证毕。

法三:拉格朗日定理

p 为质数时,所有与 p 互质的整数模 p 的非零余数构成一个乘法群,记作 (Z/pZ),它的阶是 p1

考虑由 a 生成的循环子群,它的阶就是 a 的阶,即满足 ak1(modp) 的最小正数 k。由拉格朗日定理,有限群中任意子群的阶必整除原群的阶,也就是 kp1p1=km,mZ。于是

ap1amk(ak)m1m1(modp)

证毕。

另外,费马小定理实质上是欧拉定理的一种特殊情况,下文会谈。

回到求逆元问题上来,由于 ap11(modp),可以发现 a×ap21(modp),于是 x=ap2 就是所求的解。快速幂即可求出,时间复杂度 O(logp)

1
2
3
4
int inv(int a, int p)
{
    return fastpow(a, p - 2, p);
}

可以发现能用费马小定理的情况一定能用扩展欧几里得算法得出(pPgcd(a,p)=1),反之则不行。

递推线性求逆元

起始条件 111(modp)

p=kq+r,那么有

kq+r0(modp)(kq+r)(q1)(r1)0(modp)kr1+q10(modp)q1kr1(modp)q1pq(pmodq)1(modp)

其中 (pmodq)<p,其逆元的值已经被计算出来了。

另外,写代码的时候,我们不希望它计算后是个负数,因此一般写成 q1=(ppq)(pmodq)1

1
2
3
4
5
6
7
int inv[Max];
void Pre(int Max, int p)
{
    inv[1] = 1;
    F(i, 2, p - 1)
        inv[i] = (p - p / i) * inv[p % i] % p;
}

0x06 欧拉定理 & 扩展欧拉定理

欧拉定理

gcd(a,m)=1,则 aφ(m)1(modm)

证明非常类似费马小定理的证明。

把费马小定理证明的法一中的 S 改为与 m 互质的集合,其他过程同理。

把费马小定理证明的法三中的 (Z/pZ) 改为 (Z/mZ),它的阶是 φ(m),其他过程同理。

mP 时,由于 φ(m)=m1,立刻退化成费马小定理。

扩展欧拉定理

ab{abmodφ(m),gcd(a,m)=1ab,gcd(a,m)1,b<φ(m)a(bmodφ(m))+φ(m),gcd(a,m)1,bφ(m)(modm)

第一种情况可以由欧拉定理直接得到,第二种情况的意思是无需使用扩展欧拉定理计算,时间复杂度可以接受,第三种情况证明太长不看。

例题

2↑↑modp

对于指数很大的数要首先想到扩展欧拉定理。由于 p 是任意给定的,不能保证 gcd(2,p)=1,因此采用第三行式子(对于第一种情况依然可行)。于是

2↑↑22↑↑modφ(p)+φ(p)(modp)

注意看,此时对 2↑↑ 的模数由 p 变为了 φ(p),不难想到模数最终会递归到 1,此时返回 0 即可。下面计算此过程的复杂度。由 φ(n) 的性质,

  1. n>12φ(n)

  2. φ(2n)n

可知,最坏情况下每两次迭代 φ(p) 先变成偶数再减半,这个时间复杂度是 O(logp) 的,可以通过。

1
2
3
4
5
6
int f(int p)
{
    if(p == 1) return 0;
    int Phi = phi(p);
    return fastpow(2, f(Phi) + Phi, p);
}

0x07 中国剩余定理 & 扩展中国剩余定理

中国剩余定理

中国剩余定理是用来求解如下形式的一元线性同余方程组:

{xa1(modb1)xa2(modb2)xan(modbn)

这里 bi 两两互质

推导过程

考虑把方程组分解,若能分别解出下列 n 个方程的解,

{x1a1(modb1)x10(modb2)x10(modbn){x20(modb1)x2a2(modb2)x20(modbn){xn0(modb1)xn0(modb2)xnan(modbn)

就能得到 x=i=1nxi

以第一个方程为例,可以进一步转换:

{x1a1(modb1)x10(modb2)x10(modbn){y11(modb1)y10(modb2)y10(modbn)

求出了 y1 立刻就有 x1=a1×y1。继续化简,有 {y11(modb1)y10(modi=2nbi)

pi=jibj,于是 y1=1+kb1=mp1,问题也就转化为了解 mp1kb1=1 这个不定方程。注意到前提条件保证 bi 两两互质,因此 gcd(p1,b1)=1,可以使用 exgcd 求解。

于是便可以按这种方法,求出上面的 n 个方程组的解,然后便得到了原方程的整数解。

结论

  1. 计算所有模数的积 b=i=1nbi

  2. 对于第 i 个方程,

    1. 计算 pi=bbi

    2. exgcd 计算 pi 在模 bi 意义下的逆元 pi1

    3. 计算 ci=pi×pi1不要对 bi 取模

  3. 唯一解为 x=i=1nai×ci(modb)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define int __int128
signed main()
{
    read(n);
    F(i, 1, n)
        read(b[i], a[i]), B *= b[i];
    int ans = 0;
    F(i, 1, n)
    {
        int p = B / b[i];
        ans += a[i] * p * inv(p, b[i]);
        // 因为用了 __int128 很充裕,如果是 long long 要龟速乘且每步取模
    }
    write('\n', ans % B);
    return 0;
}

扩展中国剩余定理

中国剩余定理无法处理模数不互质的情况,这是中国剩余定理的原理所决定的。它的解中 “pi 在模 bi 意义下的逆元 pi1” 在 pi,bi 不互质的情况下,根本不存在。因此我们需要完全放弃中国剩余定理的思路,转到合并方程的方向上来。

推导过程

仅考虑两个方程的方程组,

{xa1(modb1)xa2(modb2)

它等价于 x=k1b1+a1=k2b2+a2,移项得 k1b1k2b2=a2a1。这显然是一个不定方程,利用裴蜀定理确定是否有解:如果 gcd(b1,b2)a2a1 则无解。

利用 exgcd 可以求出不定方程的解(前面例题有讲),于是构造出了一个这样的 x0。但是通解是什么呢?根据线性代数的知识,通解等于特解加齐次解,它对应的齐次方程就是

{xh0(modb1)xh0(modb2)

显然它的解是 xh=klcm(b1,b2),于是得到了 x=x0+klcm(b1,b2),也就是

xx0(modlcm(b1,b2))

这就是两个同余方程合并为一个的结果。

因此,反复的将两个方程合并为一个,最终剩余的结果就是解系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#define int __int128
typedef pair<int, int> pii;
#define mp(a, b) make_pair(a, b)
pii a[maxn];
pii merge(pii x, pii y)
{
    int a1 = x.first, b1 = x.second;
    int a2 = y.first, b2 = y.second;
    int u = a2 - a1, d = gcd(b1, b2);
    if(u % d != 0)
        assert(1);
    int l = b1 * b2 / d;
    int p, q;
    exgcd(b1, b2, p, q);
    p = (p % b2 + b2) % b2;
    p *= u / d;
    int ans = a1 + p * b1;
    ans = (ans % l + l) % l;
    return mp(ans, l);
}
signed main()
{
    read(n);
    F(i, 1, n)
        read(a[i].second, a[i].first);
    F(i, 2, n)
        a[i] = merge(a[i - 1], a[i]);
    write('\n', a[n].first);
    return 0;
}

0x08 BSGS & 扩展 BSGS

BSGS

BSGS(Baby-Step Giant-Step,大步小步算法)常用于求解离散对数问题。具体的,它能在 O(m) 的时间复杂度内求解

axb(modm)

其中 gcd(a,m)=1

推导过程

正如“大步小步”的名字一样,主要在于先小步小步的处理,然后大步大步的处理,跟分块相似。

x=qB+r,就有

aqB+rbaqBb×ar(modm)

预处理每个 b×ar 到哈希表,枚举每个 q 即可。考虑时间复杂度,显然让 B=m 时两边的处理次数平均最少,为 O(m)

本文由作者按照 CC BY 4.0 进行授权