数论三合一

Abstract

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

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

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


a x b mod p

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

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

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

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

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

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

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

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


a x b mod p

练习题:BZOJ-2480BZOJ-3122

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

根据 Fermat Theorem,我们知道 x 一定在 0 p 1 之间。
所以令 x = A p B ,其中 A , B 均在 0 p
则有 a A p B b mod p a A p b a B mod p

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

在这儿我用了 Hash表 来维护,且从小到大枚举 A , B ,复杂度为 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;
}

x a b mod p

练习题:BZOJ-1319

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

显然, b = 0 x = 0 .

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

那么 x a b mod p ( g c ) a b mod p ( g a ) c b mod p

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

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

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

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

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

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

Theorem 4:若 a 为模 n 的原根,且 t a n 的次数,那么对于任意整数 d a d 1 mod n ,那么 t d .

PROOF
d = t q + r   ( 0 r < a ) .
a d a t q + r ( a t ) q a r a r 1 mod n .
根据 Defination 1 r = 0 .
d = t q t d .
Q.E.D

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

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

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

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

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

看上去是不是数量挺多的,且好像也很随机的样子(匿了)…

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

//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.

猜你喜欢

转载自blog.csdn.net/steaunk/article/details/78988376