原根相关

原根,定义为,对一给定模数p(p是奇质数),若有一g,满足 g 1 − p − 1 g^{1-p-1} g1p1两两不同,则称g为p的原根。

不会证明的结论QAQ:
奇质数p一定存在原根,而且通过打表我们可以发现最小的原根都很小。

task1:如何判断一个数x是否为p的一个原根?

费马小定理: x p − 1 = 1 ( p 为 质 数 ) x^{p-1}=1(p为质数) xp1=1(p)

假设 x 1 − p − 1 x^{1-p-1} x1p1不是互不相同,设有一最小的y使 x y = 1 x^y=1 xy=1,则必有 y &lt; p − 1 y&lt;p-1 y<p1

证明非常简单:
假设一个数 i i i ( i ∗ x )   m o d   p (i*x)~mod~p (ix) 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 &lt; p − 1 y|p-1,y&lt;p-1 yp1,y<p1,所以一定有一 p / p i p/p_i p/pi是y的倍数。

又因为 x y ∗ y ′ = ( x y ) y ′ = 1 y ′ = 1 x^{y*y&#x27;}=(x^{y})^{y&#x27;}=1^{y&#x27;}=1 xyy=(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} g1p1是互不相同的。

所以若有其他原根 g ′ ≠ g g&#x27;≠g g̸=g,不妨用 g x = g ′ g^x=g&#x27; gx=g的形式来表示

这样有什么好处呢?

g ′ 1 − p − 1 g&#x27;^{1-p-1} g1p1互不相同,相当于 ( g x ) 1 − p − 1 = g x ∗ ( 1 − p − 1 ) (g^{x})^{1-p-1}=g^{x*(1-p-1)} (gx)1p1=gx(1p1)互不相同,即 x ∗ ( 1 − p − 1 ) ( m o d   ( p − 1 ) ) x*(1-p-1)(mod~(p-1)) x(1p1)(mod (p1))互不相同,那显然 g c d ( x , p − 1 ) = 1 gcd(x,p-1)=1 gcd(x,p1)=1,那么x的个数即为 φ ( p − 1 ) φ(p-1) φ(p1)

所有的原根都可以用 g x ( g c d ( x , p − 1 ) = 1 ) g_{x}(gcd(x,p-1)=1) gx(gcd(x,p1)=1)的形式表示,特殊地,当x=1时,就是g自己。

实际运用:找NTT模数和对应原根

我们首先要清楚NTT模数是什么样的:
p是NTT模数,则p是质数,可以分解为 p = 2 x ∗ y + 1 p=2^x*y+1 p=2xy+1,且x比较大,一般x>=21

不难想到枚举x,y,然后判断 p = 2 x ∗ y + 1 p=2^x*y+1 p=2xy+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);
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值