原根,定义为,对一给定模数p(p是奇质数),若有一g,满足g1−p−1g^{1-p-1}g1−p−1两两不同,则称g为p的原根。
不会证明的结论QAQ:
奇质数p一定存在原根,而且通过打表我们可以发现最小的原根都很小。
task1:如何判断一个数x是否为p的一个原根?
费马小定理:xp−1=1(p为质数)x^{p-1}=1(p为质数)xp−1=1(p为质数)
假设x1−p−1x^{1-p-1}x1−p−1不是互不相同,设有一最小的y使xy=1x^y=1xy=1,则必有y<p−1y<p-1y<p−1
证明非常简单:
假设一个数iii向(i∗x) mod 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=∏piqip=\prod p_i^{q_i}p=∏piqi,因为y∣p−1,y<p−1y|p-1,y<p-1y∣p−1,y<p−1,所以一定有一p/pip/p_ip/pi是y的倍数。
又因为xy∗y′=(xy)y′=1y′=1x^{y*y'}=(x^{y})^{y'}=1^{y'}=1xy∗y′=(xy)y′=1y′=1,所以只需要判断有没有xp/pi=1x^{p/p_i}=1xp/pi=1就行了。
复杂度可以看作O(log22p)O(log_{2}^2p)O(log22p)
task2:原根的个数=φ(p-1)
假设我们已经找到了最小的原根g,我们知道g1−p−1g_{1-p-1}g1−p−1是互不相同的。
所以若有其他原根g′≠gg'≠gg′̸=g,不妨用gx=g′g^x=g'gx=g′的形式来表示
这样有什么好处呢?
若g′1−p−1g'^{1-p-1}g′1−p−1互不相同,相当于(gx)1−p−1=gx∗(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)(mod (p−1))x*(1-p-1)(mod~(p-1))x∗(1−p−1)(mod (p−1))互不相同,那显然gcd(x,p−1)=1gcd(x,p-1)=1gcd(x,p−1)=1,那么x的个数即为φ(p−1)φ(p-1)φ(p−1)。
所有的原根都可以用gx(gcd(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=2x∗y+1p=2^x*y+1p=2x∗y+1,且x比较大,一般x>=21
不难想到枚举x,y,然后判断p=2x∗y+1p=2^x*y+1p=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);
}
}