zoukankan      html  css  js  c++  java
  • 【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
    
    ");
    		}
    		else {
    			printf("%d
    ", 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("
    ");
    		}
    	}
    	
    	return 0;
    }
    
  • 相关阅读:
    mac 安装mysql. 使用brew install mysql 报错
    当忙碌成为借口的时候
    MySQL数据库导入导出详解[转发]
    一些模拟浏览器自动Post到服务器的工具(备忘)
    [转]一句css代码让你的网站变灰,一起悼念地震中逝去的生命!
    MYSQL命令行常用操作
    国外达人收集的Cheet Sheet
    SQL Server:将6字节的十六进制页面号转化成【文件号:页面号】格式函数
    javaWEB核心
    电商
  • 原文地址:https://www.cnblogs.com/Sakura-TJH/p/luogu_P6091.html
Copyright © 2011-2022 走看看