题目链接
前置知识
定义
a
a
a 模
b
b
b 的阶为使
a
n
≡
1
(
m
o
d
b
)
a^n\equiv 1\pmod b
an≡1(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)
1≤a<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(k∈N+) 或 2 p k ( k ∈ N + ) 2p^k(k\in \mathbb N^+) 2pk(k∈N+) 的数才有原根,并且这类数一定有原根,其中 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也要用(