1. 程式人生 > 其它 >數論預習筆記-原根

數論預習筆記-原根

模板題面

【模板】原根

預習筆記

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;
}