原根与阶
广告。
对于
原根与阶的定义天然地把我们的研究范围限制在缩系之中,这也是十分自然地,因为只有缩系中的数有逆元,是
我们下面给出一些阶的性质,他们大都是比较显然的:
- 对于
n ,若a^n \equiv 1 \pmod p 成立,当且仅当\delta_p(a) \mid n 。\delta_p(a^k) = \frac{[\delta_p(a), k]}{k} = \frac{\delta_p(a)}{(\delta_p(a), k)}
原根个数定理
对于
下面,我们考虑这个缩系中阶为
原根存在定理
我们先需要重提一下 CRT,它事实上阐释了下面的事情:
其中
有什么用呢?它让我们能合并两个循环群。比如
接着我们终于能说明怎样的数存在原根了:
正整数
n 存在原根\iff n = 2, 4, p^k, 2p^k ,其中p 是非 2 的素数且k \ge 1 。
必要性:
-
-
首先我们说明 $n = p$ 有原根。\ 假设存在一个阶为 $d$ 的数 $g$,设其生成的循环子群为 $\langle g\rangle$,它的阶也是 $d$,这个 $d$ 首先满足 $d \mid p - 1$。与上面的个数定理相同,$\langle g\rangle$ 中同样存在 $\varphi(d)$ 个数的阶是 $d$。而我们知道 $x^d \equiv 1 \pmod p$ 的解至多 $d$ 个,而 $\langle g \rangle$ 已经提供了 $d$ 个。因此可以说明,所有阶为 $d$ 的元素都是方程的根,也都是 $\langle g \rangle$ 中元素,正好有 $\varphi(d)$ 个。这也就是说,阶为 $d$ 的元素要么为 $\varphi(d)$ 要么为 $0$。\ 而我们知道 $\sum_{d \mid (p - 1)} \varphi(p) = p - 1$,而缩系之中正好就有 $p - 1$ 个元素。由于阶为 $d$ 的集合之间两两不交,因此每个 $d$ 都必须取到 $\varphi(d)$ 而不能是 $0$,否则就凑不出 $p - 1$ 个数了。这就说明了,一定存在阶为 $p - 1$ 的数,且有 $\varphi(p - 1)$ 个。\ 接着说明 $n = p^k (k\ge 2)$ 有原根的部分,~~由于笔者自己还不会~~,也与前文关系不大,欢迎大家移步[这一篇](https://www.luogu.com.cn/article/py24mb57)来看。 -
此时 $(\mathbb{Z} / n\mathbb{Z})^{×} \cong (\mathbb{Z} / 2\mathbb{Z})^{×}\times(\mathbb{Z} / p^k\mathbb{Z})^{×}$,后面二者都是循环群,并且二者的阶的最大公因数 $(\varphi(2), \varphi(p^k)) = 1$,因此前者也是循环群,也存在原根。
充分性:
-
事实上此时对于 $(\mathbb{Z}/n\mathbb{Z})^{\times} \cong C_2 \times C_{2^{k - 2}}$。这是因为 $(\mathbb{Z}/n\mathbb{Z})^{\times}$ 中首先有阶为 $2$ 的元素 $-1$,同时还有阶为 $2^{k - 2}$ 的元素 $5$,他们两者生成子群的交为 $\{1\}$。需要注意的是 $-1$ 并不属于 $5$ 的生成子群,因为下面我们将看到 $5$ 的生成子群中阶为 $2$ 的元素(上面的个数定理告诉我们只有一个)是 $5^{2^{k - 3}} \equiv 1 + 2^{k - 1}$ 在 $k\ge 3$ 时 $\not\equiv -1 \pmod{2^k}$。这就刚好覆盖了所有 $2^{k - 1}$ 个数。而我们知道,这两个循环群阶的 gcd 为 $2$ 并不互质,因此直积后不是一个循环群。\ 下面我们说明 $5$ 的阶是 $2^{k - 2}$。这是因为对 $k\ge 3,5^{2^{k - 3}} \equiv 1 + 2^{k - 1} \not\equiv 1 \pmod {2^k}$,而 $5^{2^{k-2}} \equiv (1 + 2^{k - 1})^2 \equiv 1 \pmod {2^k}$。\ 那么现在就要说明对 $k\ge 3,5^{2^{k - 3}} \equiv 1 + 2^{k - 1}$。我们设 $m = 2^{k - 3}$,可知 $5^m = (1 + 4)^m = \sum_{i = 0}^{m}{m \choose i}4^i$。对 $i = 0$ 的项,贡献了 $1$;对 $i = 1$ 的项,代入得贡献了 $2^{k - 1}$;对 $i \ge 2$ 的项,我们希望说明其为 $2^k$ 的倍数。\ 那么考虑算 $\nu_2({m \choose i}4^i) = 2i + \nu_2({m \choose i}) = 2i + \nu_2(m!) - \nu_2(i!) - \nu_2((m - i)!)$,我们需要该式 $\ge k$。接着由 Legendre 定理,原式后三项等于 $(m - S_2(m)) -(i - S_2(i)) - (m - i - S_2(m - i))=S_2(i) + S_2(m - i) - 1$。\ 我们接着设 $t = \nu_2(i)$,也即 $i = 2^t \times r$,其中 $r$ 是奇数。那么 $S_2(m - i) = S_2((m - 1) - i + 1)$。注意到 $(m - 1) - i$ 是 $i$ 的所有二进制位取反后的结果($m$ 是二的幂次),再 $+1$ 之后,其原本最后末位 $t$ 个 $0$ 取反成 $1$ 后加 $1$ 又全都变回了 $0$,然后最后第 $t + 1$ 位上变成了 $1$。所以 $S_2(m - i) = k - 3 - S_2(i) - t + 1 = k - 2 - S_2(i) - t$。\ 带回最开始的原式,得到 $\nu_2({m \choose i}4^i)= 2i + S_2(i) + (k - 2 - S_2(i) - t) - 1=k-3-\nu_2(i)+2i$。当 $i = 2$ 时,原式等于 $k$。当 $i = 3$ 时,原式等于 $k + 3$。对于 $i \ge 4$,此时 $2i-\nu_2(i) \ge 2i - \log_2(i) \ge 2 \times 4 - 2 = 6$,原式就 $\ge k + 3$。这样就证完了。 -
我们将其设为 $p, q$。由于 $C_{p^r} \times C_{q^o}$ 的阶分别为 $(p - 1)p^{r - 1}$,$(q - 1)q^{o - 1}$ 必然有公因数 $2$。因此其不为循环群。 -
思路同上,显然 gcd 不为 1。
求解原根
一个结论是说如果
接下来我们考虑从一个原根找出所有
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 5, V = 1e6;
int T, n, d, phi[N], pri[N], cnt, f[N];
bitset<N> vis, fl;
void init(){
phi[1] = 1;
for(int i = 2; i <= V; ++i){
if(!vis[i]){
pri[++cnt] = i;
phi[i] = i - 1;
f[i] = i;
}
for(int j = 1; j <= cnt; ++j){
if(i * pri[j] > V) break;
vis[i * pri[j]] = 1;
if(i % pri[j] == 0){
f[i * pri[j]] = f[i] * pri[j];
phi[i * pri[j]] = (pri[j] - 1) * f[i] * phi[i / f[i]];
break;
}
f[i * pri[j]] = pri[j];
phi[i * pri[j]] = phi[i] * phi[pri[j]];
}
}
fl[2] = fl[4] = 1;
for(int i = 2; i <= cnt; ++i){
for(int k = pri[i]; k <= V; k *= pri[i]){
if(2 * k <= V) fl[2 * k] = 1;
fl[k] = 1;
}
}
}
int qpow(int a, int b, int mod){
int ret = 1;
for(; b; b >>= 1, a = a * a % mod){
if(b & 1) ret = ret * a % mod;
}
return ret;
}
bool check(int g, int n){
int x = phi[n];
for(int o = 2; o <= x; ++o){
if(x % o == 0){
while(x % o == 0) x /= o;
if(qpow(g, phi[n] / o, n) == 1) return 0;
}
}
return qpow(g, phi[n], n) == 1;
}
void solve(){
cin >> n >> d;
if(!fl[n]) cout << 0 << "\n\n";
else{
cout << phi[phi[n]] << '\n';
int g;
for(int i = 1; i < n; ++i){
if(check(i, n)){
g = i;
break;
}
}
vector<int> rge;
int x = g;
for(int i = 1; i <= phi[n]; ++i, x = x * g % n){
if(__gcd(i, phi[n]) == 1) rge.push_back(x);
}
sort(rge.begin(), rge.end());
for(int i = d - 1; i < rge.size(); i += d)
cout << rge[i] << ' ';
cout << '\n';
}
}
signed main(){
cin.tie(nullptr)->sync_with_stdio(0);
init();
cin >> T;
while(T--) solve();
return 0;
}
::::