原根

这个的用处还是非常大的

  • 在模意义下的乘法可以通过原根转换为加法。
  • NTT 需要原根。

定义

,且 。则称 的一个原根。

是否有原根

只有 ,其中 为奇素数 , 为正整数。

计算最小原根

这里直接使用最常见的作法。由小到大枚举 。那么只需要判断 就可以了,如果有一个为 ,则这个数不是 的一个原根。根据一些玄学引论, 的最小原根是 级别的,所以即使 很大,这个的时间复杂度仍然可以接受。

证明

考虑反证,令 而且 。且 。根据欧拉定理 。所以我们可以解一组二元一次方程,根据裴蜀定理 。所以 。所以必定满足至少一组 。这与上文相违背,所以得证。

代码

#include<bits/stdc++.h>
using namespace std;
int read() {
    int x = 0,f = 0;char ch = getchar();
    while(!isdigit(ch)) {if(ch == '-') f = 1;ch = getchar();}
    while(isdigit(ch)) {x = x * 10 + ch - '0';ch = getchar();}
    return f ? -x : x;
}
const int N = 10101010;
int a[50],n;
int varphi(int x) {
    int ans = x;
    for(int i = 2;i * i <= x;i++) {
        if(x % i) continue;
        ans = ans / i * (i - 1) ;
        while((x % i) == 0) x = x / i;
    }
    if(x > 1) ans = ans / x * (x - 1) ;
    return ans;
}
int qpow(int a,int b,int mod) {
    int x = 1;
    for(;b;b>>=1,a = 1LL * a * a % mod) {
        if(b & 1) x = 1LL * a * x % mod;
    }
    return x;
}
int p[N],vis[N];
void solve(int limit) {
    for(int i = 2;i <= limit;i++) {
        if(!vis[i]) p[++p[0]] = i;
        for(int j = 1;j <= p[0] && p[j] * i <= limit;j++) {
            vis[i * p[j]] = 1;
            if((i % p[j]) == 0) break;
        }
    }
}
int main() {
    solve(1e7);
    int T = read();
    while(T--) {
        memset(a,0,sizeof(a));
        n = read();
        int phi = varphi(n);
        int m = phi,ans = 0;
        for(int i = 1;i <= p[0] && p[i] * p[i] <= phi;i++) {
            if(phi % p[i]) continue;
            a[++a[0]] = p[i];
            while((phi % p[i]) == 0) phi = phi / p[i];
        }
        if(phi > 1) a[++a[0]] = phi;
        for(int i = 1;i <= m;i++) {
            int check = 0;
            for(int j = 1;j <= a[0];j++) {
                if(qpow(i,m/a[j],n) == 1) check = 1;
            }
            if(check == 0) {
                ans = i;break;
            }
        }
        cout << ans << endl;
    }
}