Abstract
本文将探讨与下列三个同余方程有关的问题,并进行一些数论相关知识的拓展。
分别求出
的最小值。
注:如非特别说明,则本文中所有的 均表示为素数,且涉及到的数均为正整数。
Euler Theorem:
根据欧拉定理和欧拉函数的定义,则有费马定理:
Fermat Theorem:
Theorem 1:当且仅当 , 有解。
PROOF(不严谨)
易知
.
根据 Bézout’s identity:
存在整数解当且仅当
.
所以当且仅当
,
有解.
Q.E.D
所以该同余方程一定有解。
Theorem 2:若
,则
有恰有
个不同的解.
Conclusion 1:对任意
,如果
,则
对模
有唯一解.
所以该同余方程只有唯一解。
.
有了 Fermat Theorem ,我们就知道了
。
用快速幂直接计算
就是
的最小值了。
这里我们需要用到一个算法:大步小步(Baby Step Giant Step).
它是用于解决
这类问题的。
根据 Fermat Theorem,我们知道
一定在
之间。
所以令
,其中
均在
。
则有
。
于是我们可以得到这样的一个(类似双向搜索的)做法,先枚举 算出所有的 ,用数据结构维护,再逐一计算 ,寻找是否有与之相等的 ,从而就可以找到最小值了。
在这儿我用了 Hash表 来维护,且从小到大枚举 ,复杂度为 。
//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;
}
练习题:BZOJ-1319
要想解决这个问题,需要先搞懂 是怎么解决的。
显然, 时 .
然后我们假设能找到一个数
,
满足
且
,
,
即
与
的数是一一对应的。
那么
其中 是变量, 是常量,似曾相识?看看前文的式子 ,问题就这样被划归为第二问了。
那么怎么找到这样一个
呢?
先定义一下。
Defination 1:若
,
, 则使得
,成立的最小正整数
叫作
模
的次数;当
时,我们称
为模
的原根。
Conclusion 2:在模
意义下,如果
是原根,则对于任意
都可以表示为
的一个幂。
所以我们要找的数 就是模 的原根。
Theorem 3:对于所有的素数 和正整数 ,只有当 有原根。
所以显然我们是可以找到 的。
Theorem 4:若 为模 的原根,且 为 模 的次数,那么对于任意整数 , ,那么 .
PROOF
令
.
.
根据 Defination 1,
.
.
Q.E.D
看到这我们就可以有所启发了。
由 Fermat Theorem 知,
,
再根据 Theorem 4 我们明白了,如果存在
使得
,那么
一定是
的因数。
那么若
是模
的原根,那么
。
综上,我们得到了一个基于原根的分布的算法,我们从 到 的去枚举 , 且 满足 ,那么 一定不是原根。
复杂度为 (你可以认为 是一个小常数)。
Theorem 5:若 为模 的原根,则模 的原根的个数为 ,并且
看上去是不是数量挺多的,且好像也很随机的样子(匿了)…
至此我们将第三问化归为了第二问,也找到了一个原根,该问题圆满解决!
//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.