数论三合一

Abstract

本文将探讨与下列三个同余方程有关的问题,并进行一些数论相关知识的拓展。

axbmodp a x ≡ b mod p
axbmodp a x ≡ b mod p
xabmodp x a ≡ b mod p
分别求出 x x 的最小值。

注:如非特别说明,则本文中所有的 p 均表示为素数,且涉及到的数均为正整数。


axbmodp a x ≡ b mod p

Euler Theorem aφ(n)1modn,(a,n)=1. a φ ( n ) ≡ 1 mod n , ∀ ( a , n ) = 1.

根据欧拉定理和欧拉函数的定义,则有费马定理:
Fermat Theorem ap11modp a p − 1 ≡ 1 mod p

Theorem 1:当且仅当 (a,n)b ( a , n ) ∣ b axbmodn a x ≡ b mod n 有解。

PROOF(不严谨)
易知 axbmodnax+ny=b a x ≡ b mod n ⇒ a x + n y = b .
根据 Bézout’s identity ax+ny=b a x + n y = b 存在整数解当且仅当 (a,n)b ( a , n ) ∣ b .
所以当且仅当 (a,n)b ( a , n ) ∣ b axbmodn a x ≡ b mod n 有解.
Q.E.D

所以该同余方程一定有解。

Theorem 2:若 (a,n)b ( a , n ) ∣ b ,则 axbmodn a x ≡ b mod n 有恰有 (a,n) ( a , n ) 个不同的解.
Conclusion 1:对任意 n>1 n > 1 ,如果 (a,n)=1 ( a , n ) = 1 ,则 axbmodn a x ≡ b mod n 对模 n n 有唯一解.

所以该同余方程只有唯一解。

axbmodpxba1modp.
有了 Fermat Theorem ,我们就知道了 ap2a1modp a p − 2 ≡ a − 1 mod p
用快速幂直接计算 bap2 b a p − 2 就是 x x 的最小值了。


axbmodp

练习题:BZOJ-2480BZOJ-3122

这里我们需要用到一个算法:大步小步(Baby Step Giant Step).
它是用于解决 axbmodp a x ≡ b mod p 这类问题的。

根据 Fermat Theorem,我们知道 x x 一定在 0p1 之间。
所以令 x=ApB x = A ⌈ p ⌉ − B ,其中 A,B A , B 均在 0p 0 ∼ ⌈ p ⌉
则有 aApBbmodpaApbaBmodp a A ⌈ p ⌉ − B ≡ b mod p ⇒ a A ⌈ p ⌉ ≡ b a B mod p

于是我们可以得到这样的一个(类似双向搜索的)做法,先枚举 B B 算出所有的 baB,用数据结构维护,再逐一计算 aAp a A ⌈ p ⌉ ,寻找是否有与之相等的 baB b a B ,从而就可以找到最小值了。

在这儿我用了 Hash表 来维护,且从小到大枚举 A,B A , B ,复杂度为 O(q) O ( q )

//BZOJ 3122 这题还要推一下式子
#include <cstdio>
#include <cstring>
#include <cmath>
#define Min(_A, _B) (_A < _B ? _A : _B)
#define R register
const int Mod = 1000003;
namespace Steaunk
{
    struct Hash
    {
        int Point[Mod], Next[31630], To[31630], W[31630], q;
        void Add(R int u, R int v)
        { W[++q] = u; u %= Mod; Next[q] = Point[u]; Point[u] = q; To[q] = v; }
        int check(R int u)
        {
            for(R int j = Point[u % Mod]; j; j = Next[j])
                if(W[j] == u) return To[j];
            return 2139062143;
        }
        void clear(){ memset(Point, 0, sizeof(Point)); q = 0; }
    } M;
    int p, a, b, k, t;
    int Pow(R int A, R int K = p - 2)
    {
        R int d = 1;
        while(K)
        {
            if(K & 1) d = 1ll * d * A % p;
            K >>= 1;
            A = 1ll * A * A % p;
        }
        return d;
    }
    void main()
    {
        scanf("%d %d %d %d %d", &p, &a, &b, &k, &t);
        if(a == 0 || k == t || (k == 0 && b == 0))
        {
            if(k == t) puts("1");
            else if(b == t) puts("2");
            else puts("-1");
            return ;
        }
        if(a == 1 && b != 0)
        {
            t = (t - k + p) % p;
            t = 1ll * t * Pow(b) % p;
            printf("%d\n", t + 1);
            return ;
        }
        M.clear();
        R int tmp = 1ll * b * Pow(a - 1) % p;
        t = t + tmp;
        tmp += k;
        t = 1ll * t * Pow(tmp) % p;
        R int w = sqrt(p) + 1.5;
        R int v = 1;
        for(R int i = 0; i < w; i++)
        {
            tmp = 1ll * t * v % p;
            M.Add(tmp, -i + 1);
            v = 1ll * v * a % p;
        }
        R int o = Pow(a, w); v = o;
        for(R int i = 1; i <= w; i++)
        {
            R int t = M.check(v);
            if(t != 2139062143) 
            {
                printf("%d\n", i * w + t);
                return ;
            }
            v = 1ll * v * o % p;
        }
        puts("-1");
    }
}
int main()
{
    R int T;
    scanf("%d", &T);
    while(T--) Steaunk::main();
    return 0;
}

xabmodp x a ≡ b mod p

练习题:BZOJ-1319

要想解决这个问题,需要先搞懂 axbmodp a x ≡ b mod p 是怎么解决的。

显然, b=0 b = 0 x=0 x = 0 .

然后我们假设能找到一个数 g g
满足 y,z<p1 yz y ≠ z gygzmodp g y ≠ g z mod p
gc (0<c<p1) g c   ( 0 < c < p − 1 ) 1p1 1 ∼ p − 1 的数是一一对应的。

那么 xabmodp(gc)abmodp(ga)cbmodp x a ≡ b mod p ⇒ ( g c ) a ≡ b mod p ⇒ ( g a ) c ≡ b mod p

其中 c c 是变量,ga 是常量,似曾相识?看看前文的式子 axbmodp a x ≡ b mod p ,问题就这样被划归为第二问了。

那么怎么找到这样一个 g g 呢?
先定义一下。

Defination 1:若 t>1 (a,n)=1 ( a , n ) = 1 , 则使得 at1modn a t ≡ 1 mod n ,成立的最小正整数 t t 叫作 a n n 的次数;当 t=n1 时,我们称 a a 为模 n原根
Conclusion 2:在模 n n 意义下,如果 a 是原根,则对于任意 x<n x < n 都可以表示为 a a 的一个幂。

所以我们要找的数 g 就是模 p p 的原根。

Theorem 3:对于所有的素数 p>2 和正整数 e e ,只有当 n=1,2,4,pe,2pe 有原根。

所以显然我们是可以找到 g g 的。

Theorem 4t a a n 的次数,那么对于任意整数 d d ad1modn,那么 td t ∣ d .

PROOF
d=tq+r (0r<t) d = t q + r   ( 0 ≤ r < t ) .
adatq+r(at)qarar1modn a d ≡ a t q + r ≡ ( a t ) q a r ≡ a r ≡ 1 mod n .
根据 Defination 1 r=0 r = 0 .
d=tqtd ∴ d = t q ⇒ t ∣ d .
Q.E.D

看到这我们就可以有所启发了。
Fermat Theorem 知, gp11modp g p − 1 ≡ 1 mod p
再根据 Theorem 4 我们明白了,如果存在 t t 使得 gt1modp,那么 t t 一定是 p1 的因数。
那么若 g g 是模 p 的原根,那么 t=p1 t = p − 1

综上,我们得到了一个基于原根的分布的算法,我们从 2 2 p1 的去枚举 g g x|p xp x ≠ p 满足 gx1modp g x ≡ 1 mod p ,那么 g g 一定不是原根。

复杂度为 O(NAplogp)(你可以认为 NA N A 是一个小常数)。

Theorem 5:若 g g 为模 m 的原根,则模 m m 的原根的个数为 φ(φ(m)),并且

{gi(i,φ(m))=1,1i<φ(m)} { g i ∣ ( i , φ ( m ) ) = 1 , 1 ≤ i < φ ( m ) }
即为所有原根的集合。

看上去是不是数量挺多的,且好像也很随机的样子(匿了)…
update 2018.7.14:我今天去看了一下wekipidia,发现它跟我一样口糊…

至此我们将第三问化归为了第二问,也找到了一个原根,该问题圆满解决!

//BZOJ 1319
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define R register
const int Mod = 1000003;
int a, b, p, g, fac[70010], tot, S[100010], cnt, V[100010];
struct Hash
{
    int Point[Mod], Next[100010], To[100010], W[100010], q;
    void Add(R int u, R int v){ W[++q] = u; Next[q] = Point[u %= Mod]; Point[u] = q; To[q] = v; }
    void Push(R int u, R int v)
    {
        R int t = u % Mod;
        for(R int j = Point[t]; j; j = Next[j])
            if(u == W[j]) S[++cnt] = v - To[j];
    }
} M;
int Pow(R int A, R int K)
{
    R int d = 1;
    while(K)
    {
        if(K & 1) d = 1ll * d * A % p;
        K >>= 1;
        A = 1ll * A * A % p;
    }
    return d;
}
bool check(R int A)
{
    for(R int i = 1; i <= tot; i++) if(Pow(A, fac[i]) == 1) return 0;
    return 1;
}
int main()
{
    scanf("%d %d %d", &p, &a, &b);
    for(R int i = 2; i * i < p; i++)
    {
        if((p - 1) % i == 0)
        {
            fac[++tot] = i;
            if(i * i != p - 1) fac[++tot] = (p - 1) / i;
        }
    }
    for(R int i = 2; i < p; i++)
        if(check(i))
        {
            g = i;
            break;
        }
    R int G = Pow(g, a);
    R int w = sqrt(p) + 1.5, d = b;
    for(R int i = 0; i < w; i++) M.Add(d, i), d = 1ll * d * G % p;
    d = 1; R int U = Pow(G, w);
    for(R int i = 0; i <= w; i++) M.Push(d, i * w), d = 1ll * d * U % p;    
    tot = 0;
    for(R int i = 1; i <= cnt; i++) if(S[i] >= 0 && S[i] < p - 1) V[++tot] = Pow(g, S[i]);
    std::sort(V + 1, V + 1 + tot);
    R int Ans = 0;
    for(R int i = 1; i <= tot; i++) if(V[i] != V[i - 1]) Ans++;
    printf("%d\n", Ans);
    for(R int i = 1; i <= tot; i++) if(V[i] != V[i - 1]) printf("%d\n", V[i]);
    return 0;
}

参考文献

[1] Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford Stein. Introduction to Algorithms.
[2] Miskcoo. 扩展大步小步法解决离散对数问题. http://blog.miskcoo.com/2015/05/discrete-logarithm-problem.
[3] 夏静波, 张四兰, 陈建华. 高效的原根生成算法.
[4] etc.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值