题解 P6091 【【模板】原根】
效率更优秀的算法!时间复杂度
首先,原根是什么以及如何求原根这里就不再赘述了,可以移步其他题解。
求原根的过程大致分为以下5步:
1.线性筛预处理所有质数和有原根的数,
2.将
3.快速幂求出最小原根,复杂度大概是
4.通过最小原根求出所有原根,需要进行
5.排序,从小到大输出,
很容易看出,复杂度瓶颈是第4步和第5步。
第5步排序很容易优化,用计数排序代替快速排序就可以
关键是第4步。由于我们要求出所有小于
代码如下(不开O2不到1s跑完所有数据,比常规方法快了约10倍):
#include<cstdio>
const int N=1e6+7;
int pr[N],ph[N],fc[9],u[11],v[11],a[N];
bool f[N],b[N],p[N],q[N];
void init(int n){//第1步线性筛,b数组记录是否有原根
b[2]=b[4]=ph[1]=1;
int i,j,k,t=0;
for(i=2;i<=n;++i){
if(!f[i])pr[++t]=i,ph[i]=i-1;
for(j=1;k=i*pr[j],k<=n&&j<=t;++j){
f[k]=1;
if(!(i%pr[j])){
ph[k]=ph[i]*pr[j];
break;
}
ph[k]=ph[i]*ph[pr[j]];
}
}
for(i=2;i<=t;++i){
for(j=1;j*1ll*pr[i]<=n;b[j*=pr[i]]=1);
for(j=2;j*1ll*pr[i]<=n;b[j*=pr[i]]=1);
}
}
int qp(int a,int b,int p){
int r=1;
while(b){
if(b&1)r=r*1ll*a%p;
a=a*1ll*a%p,b>>=1;
}
return r;
}
int main(){
int T,n=0,m,d,i,j,k,t,o,s;
scanf("%d",&T);
for(i=1;i<=T;++i)scanf("%d%d",u+i,v+i),n=n>u[i]?n:u[i];
init(n);
for(o=1;o<=T;++o){
n=u[o],d=v[o];
if(!b[n]){
puts("0\n");
continue;
}
for(i=1,j=m=ph[n],t=0;pr[i]*pr[i]<=j;++i){
if(!(j%pr[i])){
fc[++t]=s=pr[i];
do j/=s;while(!(j%s));
for(k=s;k<=m;k+=s)p[k]=1;
}
}//第2步及第4步
if(j>1){
fc[++t]=j;
for(k=j;k<=m;k+=j)p[k]=1;
}
for(j=1;;++j){
while(qp(j,m,n)!=1)++j;
for(i=1;i<=t&&qp(j,m/fc[i],n)!=1;++i);
if(i>t)break;
}//第3步
for(t=j,i=1,s=0;i<=m;++i,t=t*1ll*j%n)if(!p[i])q[t]=1,++s;else p[i]=0;//第4步中求原根的部分
printf("%d\n",s);
for(i=1,j=0;i<n;++i){
if(q[i]){
q[i]=0,++j;
if(j==d)j=0,printf("%d ",i);
}
}//第5步
puts("");
}
return 0;
}
upd:其实可以
可以用这种方法通过 P5285 的第 15 个点 。