求原根


原根

為了簡單起見,只考慮素數的情況。(並不是只有素數才有原根

定義:對於素數 $p$,如果存在一個正整數 $1<a<p$,使得 $a^1, a^2, ..., a^{p-1}$ 模 $p$ 的值取遍 $1,2,...,p-1$ 的所有整數,稱 $a$ 是 $p$ 的一個原根(primitive root),其實就是循環群的生成元。

如果 $a^j \equiv a^i(mod \ p)$,則 $i \equiv j(mod \ {p-1})$。這里有兩個例子:

  • 3是7的原根,因為3-->2-->6-->4-->5-->1,然后開始循環
  • 2不是7的原根,因為2-->4-->1-->2-->4...,過早的循環了

注意到 $a^{p-1} \equiv 1(mod \ p)$,這個生成序列一定會包含1,且在此之前不會有循環——要是在出現1之前就循環了,就永遠不會出現1了。

也就是說,原根的循環節為 $p-1$,非原根有較小的循環節,且是 $p-1$ 的約數(因為元素的階整除群的階)。

這就是判斷原根的方法:枚舉小循環長度$b$ (它一定是 $p-1$ 的真因子),判斷是否有 $m^b \equiv 1(mod \ p)$(如果是,則表示 $m$ 不是原根)。雖然這個方法理論上並不是很優秀,但在算法競賽中已經夠用。

通俗地說,如果是原根,群的階次方才為1;如果不是原根,群的階的約數次方就會出現1.

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
ll qpow(ll a, ll b, ll p) {
  ll res = 1;
  while (b > 0) {
    if (b & 1) res = res * a % p;
    a = a * a % p, b >>= 1;
  }
  return res;
}

ll generator(ll p) {
  vector<ll> fact;
  ll phi = p - 1, n = phi;
  for (ll i = 2; i * i <= n; ++i) {
    if (n % i == 0) {
      fact.push_back(i);
      while (n % i == 0) n /= i;
    }
  }
  if (n > 1) fact.push_back(n);
  for (ll res = 2; res <= p; ++res) {
    bool ok = true;
    for (ll factor : fact) {
      if (qpow(res, phi / factor, p) == 1) {
        ok = false;
        break;
      }
    }
    if (ok) return res;
  }
  return -1;
}

int main()
{
    printf("%d\n", generator(998244353));
}

值得注意的是,原根並不是唯一的。

有結論:設群 $G=(a)$,若 $|G|=n$,則 $G=(a^r)$ 當且僅當 $(r, n)=1$,即生成元有 $\varphi (n)$ 個。

在上文中,群為 ${1, 2, ..., p-1}$ 模 $p$ 的乘法群。

例如,$p=7$ 時,3為原根,(5, 6)=1,所以 3^5 %7=5 也是原根。

 

 

參考鏈接:https://oi-wiki.org/math/primitive-root/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM