原根,定义为,对一给定模数p(p是奇质数),若有一g,满足 g 1 − p − 1 g^{1-p-1} g1−p−1两两不同,则称g为p的原根。
不会证明的结论QAQ:
奇质数p一定存在原根,而且通过打表我们可以发现最小的原根都很小。
task1:如何判断一个数x是否为p的一个原根?
费马小定理: x p − 1 = 1 ( p 为 质 数 ) x^{p-1}=1(p为质数) xp−1=1(p为质数)
假设 x 1 − p − 1 x^{1-p-1} x1−p−1不是互不相同,设有一最小的y使 x y = 1 x^y=1 xy=1,则必有 y < p − 1 y<p-1 y<p−1
证明非常简单:
假设一个数
i
i
i向
(
i
∗
x
)
m
o
d
p
(i*x)~mod~p
(i∗x) mod p连有向边,我们从i=1,沿着边走,由于在走了p-1步后回到了1,每个点只有一条出边,所以我们可以确定1走p-1步一定是重复经过一个长度为y(y|p-1)的环(p-1)/y次,所以如果不是互不相同,那么肯定可以在y(y<p-1)步时找到一个1。
这样看上去我们需要枚举p-1的所有约数来判断,好麻烦啊。
设 p = ∏ p i q i p=\prod p_i^{q_i} p=∏piqi,因为 y ∣ p − 1 , y < p − 1 y|p-1,y<p-1 y∣p−1,y<p−1,所以一定有一 p / p i p/p_i p/pi是y的倍数。
又因为 x y ∗ y ′ = ( x y ) y ′ = 1 y ′ = 1 x^{y*y'}=(x^{y})^{y'}=1^{y'}=1 xy∗y′=(xy)y′=1y′=1,所以只需要判断有没有 x p / p i = 1 x^{p/p_i}=1 xp/pi=1就行了。
复杂度可以看作 O ( l o g 2 2 p ) O(log_{2}^2p) O(log22p)
task2:原根的个数=φ(p-1)
假设我们已经找到了最小的原根g,我们知道 g 1 − p − 1 g_{1-p-1} g1−p−1是互不相同的。
所以若有其他原根 g ′ ≠ g g'≠g g′̸=g,不妨用 g x = g ′ g^x=g' gx=g′的形式来表示
这样有什么好处呢?
若 g ′ 1 − p − 1 g'^{1-p-1} g′1−p−1互不相同,相当于 ( g x ) 1 − p − 1 = g x ∗ ( 1 − p − 1 ) (g^{x})^{1-p-1}=g^{x*(1-p-1)} (gx)1−p−1=gx∗(1−p−1)互不相同,即 x ∗ ( 1 − p − 1 ) ( m o d ( p − 1 ) ) x*(1-p-1)(mod~(p-1)) x∗(1−p−1)(mod (p−1))互不相同,那显然 g c d ( x , p − 1 ) = 1 gcd(x,p-1)=1 gcd(x,p−1)=1,那么x的个数即为 φ ( p − 1 ) φ(p-1) φ(p−1)。
所有的原根都可以用 g x ( g c d ( x , p − 1 ) = 1 ) g_{x}(gcd(x,p-1)=1) gx(gcd(x,p−1)=1)的形式表示,特殊地,当x=1时,就是g自己。
实际运用:找NTT模数和对应原根
我们首先要清楚NTT模数是什么样的:
p是NTT模数,则p是质数,可以分解为
p
=
2
x
∗
y
+
1
p=2^x*y+1
p=2x∗y+1,且x比较大,一般x>=21
不难想到枚举x,y,然后判断 p = 2 x ∗ y + 1 p=2^x*y+1 p=2x∗y+1是否为质数,然后再从小到大枚举数,判断是否为原根,找到即退即可。
示例:
#include<cstdio>
#define pp printf
#define ll long long
#define fo(i, x, y) for(int i = x; i <= y; i ++)
using namespace std;
int gcd(int x, int y) {
return !y ? x : gcd(y, x % y);
}
ll ksm(ll x, ll y, const ll mo) {
ll s = 1;
for(; y; y /= 2, x = x * x % mo)
if(y & 1) s = s * x % mo;
return s;
}
int p;
int u[20], v[20], u0;
void fen(int x) {
u0 = 0;
for(int i = 2; i * i <= x; i ++) if(x % i == 0) {
u[++ u0] = i; v[u0] = 0;
while(x % i == 0) x /= i, v[u0] ++;
}
if(x > 1) u[++ u0] = x, v[u0] = 1;
}
int phi(int x) {
fen(x);
fo(i, 1, u0) x = x / u[i] * (u[i] - 1);
return x;
}
int p2, gp, g;
int main() {
fo(i, 21, 30) fo(j, 1, 1024) {
ll s = (1ll << i) * j + 1;
if(s >= (1ll << 31) || s < 9e8) continue;
p = s;
p2 = phi(p);
if(p2 != p - 1) continue;
gp = phi(p2);
g = -1;
fo(i, 2, p - 1) if(gcd(i, p) == 1) {
int bz = 1;
fo(j, 1, u0) if(ksm(i, p2 / u[j], p) == 1) {
bz = 0; break;
}
if(bz) { g = i; break;}
}
pp("%d %d %d\n", p, g, gp);
}
}