P6091 【模板】原根

题目链接

Link:P6091

前置知识

定义 a a a b b b 的阶为使 a n ≡ 1 ( m o d b ) a^n\equiv 1\pmod b an1(modb) 的最小正整数 n n n,记作 δ b ( a ) \delta_b(a) δb(a)
定义 a a a b b b 的原根,当且仅当 1 ≤ a < b ∧ δ b ( a ) = φ ( b ) 1\leq a< b\land\delta_b(a)=\varphi(b) 1a<bδb(a)=φ(b)

题目大意

求正整数 n n n 的所有原根。

解题思路

基础思路

判定一个数有没有原根,有这么一个定理:

只有 1 , 2 , 4 1,2,4 1,2,4 或形如 p k ( k ∈ N + ) p^k(k\in \mathbb N^+) pk(kN+) 2 p k ( k ∈ N + ) 2p^k(k\in \mathbb N^+) 2pk(kN+) 的数才有原根,并且这类数一定有原根,其中 p p p 为奇素数。

并且如果一个数 n n n 有原根,它的最小原根是不会大于 n 4 \sqrt[4]{n} 4n 的。
因此我们可以考虑枚举最小原根。
确定 a a a 是不是 b b b 的原根,只要看对于 φ ( b ) \varphi(b) φ(b) 的任意质因数 p p p 是否均有 a φ ( b ) p ≢ 1 ( m o d b ) a^{\frac{\varphi(b)}{p}}\not\equiv 1\pmod b apφ(b)1(modb)
这部分预处理质因数然后快速幂。
并且,只要我们有了最小原根 a a a,则 ∀ gcd ⁡ ( x , φ ( b ) ) = 1 , a x \forall \gcd(x,\varphi(b))=1,a^x gcd(x,φ(b))=1,ax 均为 b b b 的原根。
最后排序输出就没了。

代码实现

#include <iostream>
#include <sstream>
#include <bitset>
#include <array>

using namespace std;

bitset<1000001> have_root,primitive_root;
array<int,10> pr_fact;
array<int,72000> primes;

long long fast_pow(long long base,long long exp,long long prime)
{
    long long result;
    for(result=1;exp;exp&1?result=result*base%prime:true,base=base*base%prime,exp>>=1);
    return result;
}

template<typename type>
type gcd(type a,type b)
{
    return b?gcd(b,a%b):a;
}

int main(int argc,char* argv[],char* envp[])
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	int cnt_prime=0;
	have_root[0]=have_root[1]=true;
	for(int i=2;i<=1000000;i++)
	{
		if(!have_root[i])
			primes[++cnt_prime]=i;
		for(int j=1;j<=cnt_prime&&i*primes[j]<=1000000;j++)
		{
			have_root[i*primes[j]]=true;
			if(i%primes[j]==0)
				break;
		}
	}
	have_root.reset();
	have_root[1]=have_root[2]=have_root[4]=true;
	for(int i=2;i<=cnt_prime;i++)
		for(long long tmp=primes[i];tmp<=1000000;tmp*=primes[i])
			have_root[tmp]=true,(tmp*2<=1000000)&&(have_root[2*tmp]=true);
	int testcnt;
	cin>>testcnt;
	while(testcnt--)
	{
		int num,outf_pr,min_proot,varphi=1,tmp,cnt=0;
		cin>>num>>outf_pr,tmp=num;
		if(!have_root[num])
		{
			cout<<0<<"\n\n";
			continue;
		}
		primitive_root.reset();
		for(int i=1;primes[i]*primes[i]<=num;i++)
			if(num%primes[i]==0)
			{
				varphi*=primes[i]-1;
				num/=primes[i];
				while(num%primes[i]==0)
					varphi*=primes[i],num/=primes[i];
			}
		varphi*=max(1,num-1);
		num=tmp;
		tmp=varphi;
		for(int i=1;primes[i]*primes[i]<=varphi;i++)
			if(varphi%primes[i]==0)
			{
				pr_fact[cnt++]=primes[i];
				varphi/=primes[i];
				while(varphi%primes[i]==0)
					varphi/=primes[i];
			}
		if(varphi!=1)
			pr_fact[cnt++]=varphi;
		varphi=tmp;
		for(int i=1;i<num;i++)
		{
			if(fast_pow(i,varphi,num)!=1)
				goto cont;
			for(int j=0;j<cnt;j++)
				if(fast_pow(i,varphi/pr_fact[j],num)==1)
					goto cont;
			min_proot=i;
			break;
			cont:;
		}
		tmp=min_proot;
		for(int i=1;i<=varphi;i++,tmp=1ll*tmp*min_proot%num)
			if(gcd(varphi,i)==1)
				primitive_root[tmp]=true;
		tmp=0;
		ostringstream sout;
		for(int i=1;i<num;i++)
			if(primitive_root[i])
			{
				tmp++;
				if(tmp%outf_pr==0)
					sout<<i<<' ';
			}
		cout<<tmp<<'\n'<<sout.str()<<'\n';
	}
	return 0;
}

优化思路

原来的实现复杂度,如果最后对原根排序用快排,复杂度是 O ( n log ⁡ 2 n ) \Omicron(n\log_2n) O(nlog2n)
这里用计数排序,排序复杂度是 Θ ( n ) \Theta(n) Θ(n) 的。
现在,我们的复杂度是 Θ ( n + φ ( n ) log ⁡ φ ( n ) ) \Theta(n+\varphi(n)\log\varphi(n)) Θ(n+φ(n)logφ(n))。( log ⁡ \log log 是从 gcd ⁡ \gcd gcd 来的)
复杂度瓶颈是从最小原根求出其他原根的复杂度( Θ ( φ ( n ) log ⁡ φ ( n ) ) \Theta(\varphi(n)\log\varphi(n)) Θ(φ(n)logφ(n))部分),其它东西复杂度均不高于 Θ ( n ) \mathbf{\Theta(n)} Θ(n)
优化:
这一步,实际上指数就是所有与 φ ( n ) \varphi(n) φ(n) 互质的数。
于是我们可以通过以前分解过的 φ ( n ) \varphi(n) φ(n) 的质因数筛出所有与 φ ( n ) \varphi(n) φ(n) 互质的数。
用埃氏筛可以做到 Θ ( φ ( n ) log ⁡ log ⁡ log ⁡ φ ( n ) ) \Theta(\varphi(n)\log\log\log\varphi(n)) Θ(φ(n)logloglogφ(n))。现在这与 Θ ( n ) \Theta(n) Θ(n) 没有什么区别。
本步可以优化至严格 Θ ( φ ( n ) ) \Theta(\varphi(n)) Θ(φ(n)),但是由于常数原因会被埃氏筛吊着打。

代码实现

#include <iostream>
#include <sstream>
#include <bitset>
#include <array>

using namespace std;

bitset<1000001> have_root,primitive_root,varphi_coprime;
array<int,10> pr_fact;
array<int,500000> primes;

long long fast_pow(long long base,long long exp,long long prime)
{
    long long result;
    for(result=1;exp;exp&1?result=result*base%prime:true,base=base*base%prime,exp>>=1);
    return result;
}

template<typename type>
type gcd(type a,type b)
{
    return b?gcd(b,a%b):a;
}

int main(int argc,char* argv[],char* envp[])
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	int cnt_prime=0;
	have_root[0]=have_root[1]=true;
	for(int i=2;i<=1000000;i++)
	{
		if(!have_root[i])
			primes[++cnt_prime]=i;
		for(int j=1;j<=cnt_prime&&i*primes[j]<=1000000;j++)
		{
			have_root[i*primes[j]]=true;
			if(i%primes[j]==0)
				break;
		}
	}
	have_root.reset();
	have_root[1]=have_root[2]=have_root[4]=true;
	for(int i=2;i<=cnt_prime;i++)
		for(long long tmp=primes[i];tmp<=1000000;tmp*=primes[i])
			have_root[tmp]=true,(tmp*2<=1000000)&&(have_root[2*tmp]=true);
	int testcnt;
	cin>>testcnt;
	while(testcnt--)
	{
		int num,outf_pr,min_proot,varphi=1,tmp,cnt=0;
		cin>>num>>outf_pr,tmp=num;
		if(!have_root[num])
		{
			cout<<0<<"\n\n";
			continue;
		}
		primitive_root.reset();
		for(int i=1;primes[i]*primes[i]<=num;i++)
			if(num%primes[i]==0)
			{
				varphi*=primes[i]-1;
				num/=primes[i];
				while(num%primes[i]==0)
					varphi*=primes[i],num/=primes[i];
			}
		varphi*=max(1,num-1);
		num=tmp;
		tmp=varphi;
		for(int i=1;primes[i]*primes[i]<=varphi;i++)
			if(varphi%primes[i]==0)
			{
				pr_fact[cnt++]=primes[i];
				varphi/=primes[i];
				while(varphi%primes[i]==0)
					varphi/=primes[i];
			}
		if(varphi!=1)
			pr_fact[cnt++]=varphi;
		varphi=tmp;
		for(int i=1;i<num;i++)
		{
			if(fast_pow(i,varphi,num)!=1)
				goto cont;
			for(int j=0;j<cnt;j++)
				if(fast_pow(i,varphi/pr_fact[j],num)==1)
					goto cont;
			min_proot=i;
			break;
			cont:;
		}
		for(int i=0;i<cnt;i++)
			for(int j=1;j*pr_fact[i]<=varphi;j++)
				varphi_coprime[j*pr_fact[i]]=true;
		tmp=min_proot;
		int count_proot=0;
		for(int i=1;i<=varphi;i++,tmp=1ll*tmp*min_proot%num)
			if(!varphi_coprime[i])
				primitive_root[tmp]=true,count_proot++;
			else
				varphi_coprime[i]=false;
		tmp=0;
		cout<<count_proot<<'\n';
		for(int i=1;i<num;i++)
			if(primitive_root[i])
			{
				tmp++;
				if(tmp%outf_pr==0)
					cout<<i<<' ';
			}
		cout<<'\n';
	}
	return 0;
}

彩蛋

不要问我为什么要学这个东西,在洛谷立了flag而且这个东西我做P5285也要用(

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值