【模板】原根
题目链接:luogu P6091
题目大意
多组数据,每次给出 n,求它的所有原根。
为了减少输出,给出一个 d,你只要输出从小到大排之后某些位置的数。
思路
首先对原根不清楚的可以先看看这个:
——>点我<——
然后不知道怎么求最小原根的看这个:
——>点我<——
然后一开始你会想着按着找最小原根的方法,把所有的都找完。
当然,这会超时。
那我们考虑有哪些地方要弄,要优化。
首先,先不说找全部,如果一个数它没有原根,那你就要浪费时间跑全部,就很浪费。
那你考虑求出哪一些数,它是有原根的。
那这里有一个定理,就是如果一个数是原根,那么它肯定是满足这些条件中的其中一个:
它是 \(1/2/4\),或者它是 \(p^x/2p^x\)。(\(p\) 为质数)
然后你 \(1/2/4\) 直接标,然后枚举素数,以及它的次方,和它次方的倍数,标记一下就好了。
通过这个方法,你可以快速地判断一个数是否有原根。
然后我们来解决下一个问题,就是你不能直接枚举所有判断是不是原根。
这里给出一个方法,可以通过最小的原根求出所有的原根。
如果你找到了最小的原根 \(g\),那对于所有的 \(\gcd(x,\varphi(n))\),\(g^x\) 都是原根。
那你就把它求出来,排序之后按要求输出即可。
代码
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
int T, prime[1000001], n, d, phi[1000001];
int zyz[1000001], ans[1000001];
bool yes[1000001], np[1000001];
void get_prime() {//求质数
for (int i = 2; i <= 1000000; i++) {
if (!np[i]) {
prime[++prime[0]] = i;
}
for (int j = 1; j <= prime[0] && i * prime[j] <= 1000000; j++) {
np[i * prime[j]] = 1;
if (i % prime[j] == 0) break;
}
}
}
void check_have() {//看某个数时候有原根
yes[1] = 1;
yes[2] = 1;
yes[4] = 1;
for (int i = 1; i <= prime[0]; i++) {
ll now = prime[i];
while (now <= 1000000ll) {
yes[now] = 1;
if (now * 2ll <= 1000000ll) yes[now * 2] = 1;
now *= 1ll * prime[i];
}
}
}
void get_phi() {//求phi值
phi[1] = 1;
for (int i = 2; i <= 1000000; i++) {
if (!np[i]) phi[i] = i - 1;
for (int j = 1; j <= prime[0] && i * prime[j] <= 1000000; j++) {
if (i % prime[j] == 0) {
phi[i * prime[j]] = phi[i] * prime[j];
break;
}
phi[i * prime[j]] = phi[i] * (prime[j] - 1);
}
}
}
void fj(int now) {//分解质因数
for (int i = 1; prime[i] * prime[i] <= now; i++)
if (now % prime[i] == 0) {
zyz[++zyz[0]] = prime[i];
while (now % prime[i] == 0) now /= prime[i];
}
if (now > 1) zyz[++zyz[0]] = now;
}
int ksm(ll x, int y, int mo) {//求快速幂
ll re = 1ll;
while (y) {
if (y & 1) re = (re * x) % mo;
x = (x * x) % mo;
y >>= 1;
}
return re;
}
bool check(int x, int p) {//判断这个数是否是原根
if (ksm(1ll * x, phi[p], p) != 1) return 0;
for (int i = 1; i <= zyz[0]; i++)
if (ksm(1ll * x, phi[p] / zyz[i], p) == 1) return 0;
return 1;
}
int get_fir(int now) {//找到第一个原根
for (int i = 1; i < now; i++) {//逐个判断,找到就退出
if (check(i, now)) return i;
}
return 0;
}
int gcd(int x, int y) {//求最大公因子
if (!y) return x;
return gcd(y, x % y);
}
void get_all(int fir, int p) {//得到所有的原根
int now = 1;
for (int i = 1; i <= phi[p]; i++) {
now = (now * fir) % p;
if (gcd(i, phi[p]) == 1) ans[++ans[0]] = now;
}
}
int main() {
get_prime();
check_have();
get_phi();
scanf("%d", &T);
for (int times = 1; times <= T; times++) {
scanf("%d %d", &n, &d);
if (!yes[n]) {
printf("0\n\n");
}
else {
printf("%d\n", phi[phi[n]]);
zyz[0] = 0;
fj(phi[n]);
int fir = get_fir(n);
ans[0] = 0;
get_all(fir, n);
sort(ans + 1, ans + ans[0] + 1);//判断之后按要求输出
for (int i = 1; i <= phi[phi[n]] / d; i++)
printf("%d ", ans[i * d]);
printf("\n");
}
}
return 0;
}