其他分享
首页 > 其他分享> > 【luogu P6091】【模板】原根

【luogu P6091】【模板】原根

作者:互联网

【模板】原根

题目链接: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;
}

标签:原根,int,luogu,ll,1000001,re,P6091,mo
来源: https://www.cnblogs.com/Sakura-TJH/p/luogu_P6091.html