【luogu P6091】【模板】原根

【模板】原根

题目链接:luogu P6091

题目大意

多组数据,每次给出 n,求它的所有原根。
为了减少输出,给出一个 d,你只要输出从小到大排之后某些位置的数。

思路

首先对原根不清楚的可以先看看这个:
——>点我<——

然后不知道怎么求最小原根的看这个:
——>点我<——

然后一开始你会想着按着找最小原根的方法,把所有的都找完。
当然,这会超时。

那我们考虑有哪些地方要弄,要优化。
首先,先不说找全部,如果一个数它没有原根,那你就要浪费时间跑全部,就很浪费。
那你考虑求出哪一些数,它是有原根的。

那这里有一个定理,就是如果一个数是原根,那么它肯定是满足这些条件中的其中一个:
它是 1 / 2 / 4 1/2/4 1/2/4,或者它是 p x / 2 p x p^x/2p^x px/2px。( p p p 为质数)

然后你 1 / 2 / 4 1/2/4 1/2/4 直接标,然后枚举素数,以及它的次方,和它次方的倍数,标记一下就好了。
通过这个方法,你可以快速地判断一个数是否有原根。

然后我们来解决下一个问题,就是你不能直接枚举所有判断是不是原根。
这里给出一个方法,可以通过最小的原根求出所有的原根。
如果你找到了最小的原根 g g g,那对于所有的 gcd ⁡ ( x , φ ( n ) ) \gcd(x,\varphi(n)) gcd(x,φ(n)) g x g^x gx 都是原根。
那你就把它求出来,排序之后按要求输出即可。

代码

#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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值