數論預習筆記-原根
阿新 • • 發佈:2021-06-15
模板題面
預習筆記
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\)
根據原根判定定理:若一個數 \(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\)
以上就是找原根的暴力流程。
程式碼
#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;
}