数论预习笔记-原根

模板题面

【模板】原根

预习笔记

wiki
众所周知 OI 不需要证明。

首先原根的定义是:
对于互质的正整数 \(m\) 和整数 \(a\),如果 \(a\mod m\) 的阶等于 \(\varphi (m)\) 的话,就称 \(a\) 为模 \(m\) 的原根。
阶的定义是,对于互质的正整数 \(m\) 和整数 \(a\),满足 \(a^{x} \mod m = 1\) 的最小正整数 \(x\),记作 \(\delta_{m}(a)\)

据王元的证明可知,模 \(m\) 的最小原根不超过 \(m^{0.25}\),所以如果我们暴力枚举找最小原根复杂度也是可以接受的,所以我们找原根的流程就是:

  • 判断这个数是否具有原根:
    根据原根存在定理:一个数 \(m\) 存在原根当且仅当 \(m = 2,4,p^{\alpha},2p^{\alpha}\),其中 \(p\) 为奇素数,\(\alpha\) 为正整数。
    因为 \(m \leq 1e6\),而对于次方能取到最大的 \(3\) 来说不过是 12 次计算,因此我们完全可以预处理出所有素数满足某次方或某次方的 2 倍在这个范围内的值,然后就可以 O(1) 判断数 \(m\) 是否有原根。
    反之,如果我们不这样判断的话,我们在暴力枚举的时候就要枚举 \(m - 1\) 次才发现它没有原根还要加上快速幂每次的 log。
  • 从小到大枚举,找到 \(a^{\varphi (m)} \mod m = 1\)\(a\), 判断是否合法。
    根据原根判定定理:若一个数 \(g\) 是模 \(m\) 的原根,则有对于 \(\varphi (m)\) 任何大于 1 且不为自身的因数 \(p\),都有 \(g^{\varphi (m) / p} \mod m \neq 1\)
    因此我们只需要花 \(\sqrt m\) 的时间复杂度,求出 \(\varphi (m)\) 的除 1 以外的正因数,再依次判断就行了(质数要包含本身)。
  • 找到原根以后再根据一个神奇的定理找出其它的原根
    注意除了预处理所有的运算都是在模 \(m\) 意义下的。
    对于正整数 \(x\),模 \(m\) 的某原根 \(g\),如果 \(gcd(x,\varphi(m))= 1\),那么 \(g^x\) 也是模 \(m\) 的一个原根,因此我们再枚举这个 \(x\) 就好了,根据上面的定义其中 \(x\) 是小于 \(\varphi (m)\) 的正整数。

以上就是找原根的暴力流程。

代码

#include<cstdio>
#include<cctype>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 1e6 + 5;

int m[15], d[15], maxn, t, ans[N], cnt = 0;

int Prime[N], v[N], tot;

void GetPrime(int n) {
	memset(v, 0, sizeof(v)); tot = 0;
	for(int i = 2; i <= n; i++) {
		if(v[i] == 0) {
			v[i] = i; Prime[++tot]  = i;
		}
		for(int j = 1; j <= tot; j++) {
			if(Prime[j] > v[i] || Prime[j] > n / i) break;
			v[i * Prime[j]] = Prime[j];
		}
	}
}

#define LL long long

LL power(LL a, LL b, LL mod) {
	LL res = 1;
	for(; b; b >>= 1, a = a * a % mod)
		if(b & 1) res = res * a % mod;
	return res;
}

bool is[N];

void init() {
	is[2] = is[4] = true; LL x = 1;
	for(int i = 2; i <= tot; i++, x = 1)
		for(int j = 1; ; j++) {
			x = x * Prime[i];
			if(x > maxn) break;
			is[x] = true;
			if(2 * x <= maxn)
				is[2 * x] = true;
		}
}

int phi(int M) {
	int res = M;
	for(int i = 2; i * i <= M; i++)
		if(M % i == 0) {
			res = res / i * (i - 1);
			while(M % i == 0) M /= i;
		}
	if(M > 1) res = res / M * (M - 1);
	return res;
}

int fac[N], num = 0;

void divide(int n) {
	num = 0;
	for(int i = 2; i * i <= n; i++)
		if(n % i == 0) {
			fac[++num] = i;
			if(i != n / i) fac[++num] = n / i;
		}
	if(n > 1) fac[++num] = n;
}

bool check(int x, int PhiM, int M) {
	for(int i = 1; i <= num; i++)
		if(power(x, PhiM / fac[i], M) == 1) return false;
	return true;
}

int gcd(int a, int b) {
	return b == 0 ? a : gcd(b, a % b);
}

inline int read() {
	int x = 0, f = 1, c = getchar();
	for(; !isdigit(c); c = getchar())
		if(c == '-')
			f = -1;
	for(; isdigit(c); c = getchar())
		x = x * 10 + c - 48;
	return x * f;
}

int main() {
	t = read();
	for(int i = 1; i <= t; i++)
		m[i] = read(), maxn = max(maxn, m[i]),
		d[i] = read();
	GetPrime(maxn); init();
	for(int i = 1; i <= t; i++, cnt = 0) {
		if(!is[m[i]]) {
			printf("0\n \n"); continue;
		}
		else {
			int PhiM = phi(m[i]), g;
			divide(PhiM);
			for(int j = 1; j < m[i]; j++)
				if(power(j, PhiM, m[i]) == 1 && check(j, PhiM, m[i])) {
					g = j; break;
				}
			int x = 1;
			for(int j = 1; j <= PhiM; j++) {
				x = 1ll * x * g % m[i];
				if(gcd(j, PhiM) == 1) ans[++cnt] = x;
			} printf("%d\n", cnt);
			sort(ans + 1, ans + 1 + cnt);
			for(int j = d[i]; j <= cnt; j += d[i])
				printf("%d ", ans[j]);
			puts("");
		}
	}
	return 0;
}
posted @ 2021-06-15 21:56  init-神眷の樱花  阅读(276)  评论(0)    收藏  举报