传送门:这题有毒,不要点!!
题意很简单:求所有原根,那么问题来了,何为原根?
设 (a,m)=1, 满足 ax≡1(mod m) 的最小的 x,称为a对m的阶,记为
ordm(a)
当 ordm(a)=ϕ(m) 时称为a为m的原根.
原根有什么性质能帮助解这道题?
1.n有原根⇔n=2,4,pe,2pe(p为奇素数).
2.设 ϕ(m)=pr11pr22…prkk,则g是m的原根当且仅当对与所有的pi.
gϕ(m)pi≠1(mod m)
3.ordm(ad)=ordm(a)(ordm(a),d)(利用这个性质可以求出所有原根)
好了,现在为了尽量不要超时,我们先预处理素数,所以写一个素数筛
然后,利用性质1,排除那些乍一看就不符合条件数(注:就算n符合性质1的形态,也不一定有原根)至于怎么处理嘛...
if(n == 2)
{
printf("1\n");
return;
}
if(n == 4)
{
printf("3\n");
return;
}
if(!exist(n))//exist函数用来处理性质1的后两种形态
{
printf("-1\n");
return;
}
而以下是exist函数
bool exist(int n)
{
if(n % 2 == 0)//无论是2p^n还是p^n,先统统化为p^n
n /= 2;
if(!vis[n]) return 1;//vis[i] = true说明i为合数,否则为质数,该判断的意思是,如果是质数,则一定满足p^n或2p*n形态
for(int i = 3; i * i <= n; i += 2)
{
if(n % i == 0)
{
while(n % i == 0)
n /= i;
return n == 1;
}
}
return 0;
}
说来,以上是我超时的主要位置,我刚开始并没有写bool函数,而是在main函数里面如下判断
if(n == 2)
{
printf("1\n");
continue;
}
if(n == 4)
{
printf("3\n");
continue;
}
int ncopy = n;
if(n % 2 == 0)
n /= 2;
bool judge = true;
if(vis[n])
{
for(int i = 3; i * i <= n; i+=2)
{
if(n % i == 0)
{
while(n % i == 0) n /= i;
if(n == 1)
{
judge = true;
break;
}
else
{
judge = false;
break;
}
}
}
}
if(!judge)
{
printf("-1\n");
}
else
{
n = ncopy;
solve(n);
}
然后找不出哪里超时的我绝望地写了个愚蠢bool函数
bool exist(int n)
{
if(n % 2 == 0)
n /= 2;
bool judge = true;
if(vis[n])
{
for(int i = 3; i * i <= n; i += 2)
{
if(n % i == 0)
{
while(n % i == 0)
n /= i;
if(n == 1)
{
judge = true;
break;
}
else
{
judge = false;
break;
}
}
}
}
return judge;
}
这两种都超时了,至于为何,并不清楚,我只是明白,能写函数快速返回,绝对不要在主函数里面墨迹,而且,能return直接return,别玩花样...以后比赛时如果t了我就这么改,恩..
好了,接下来,我们利用性质2找到一个原根,然后利用性质3找到所有原根(性质3可以推导出,如果p是模n的原根,那么如果i与n的欧拉函数值互质,则p^i也是模n的原根)
此处有一点需要理解,模n的原根p的1~p-1次方模n的结果与1~n-1一一对应,也就是说,利用性质3找的原根模n就是原根结果
以下是完整代码
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<vector>
#include<set>
using namespace std;
typedef long long ll;
const int N = 1000000 + 5;
const int M = 1000 + 5;
vector<int>rec;
vector<int>ans;
bool vis[N];
void pre()//素数筛
{
memset(vis, false, sizeof(vis));
vis[0] = vis[1] = true;
for(int i = 2; i <= 1000000; i++)
{
if(!vis[i])
{
for(ll j = (ll)i * (ll)i; j <= 1000000; j += (ll)i)
{
vis[j] = true;
}
}
}
}
int euler(int n)//求欧拉函数值
{
if(!vis[n]) return n - 1;
int res = n, a = n;
for(int i = 2; i * i <= a; i++)
{
if(a % i == 0)
{
res = res / i * (i - 1);
while(a % i == 0) a /= i;
}
}
if(a > 1) res = res / a * (a - 1);
return res;
}
void ac(int n)//求数n的所有因数,一开始我求所有质因数,然后想像性质2一样用欧拉函数值一个一个除,进行判断,结果T了,比赛时如果遇到这种情况,我就改成求所有因数,然后直接判断
{
rec.clear();
if(!vis[n]) return;
for(int i = 2; i*i <= n; i++)
{
if(n % i == 0)
{
rec.push_back(i);
if(i * i != n)
rec.push_back(n / i);
}
}
}
int power(int x, int a, int mod)//快速幂
{
ll res = 1;
ll tmp = x;
while(a)
{
if(a & 1)
res = res * tmp % mod;
tmp = tmp * tmp % mod;
a >>= 1;
}
return (int)res;
}
int gcd(int a, int b)//欧几里得求最大公约数
{
if(a < b)
swap(a, b);
if(b == 0)
return a;
return gcd(b, a % b);
}
bool exist(int n)
{
if(n % 2 == 0)
n /= 2;
if(!vis[n]) return 1;
for(int i = 3; i * i <= n; i += 2)
{
if(n % i == 0)
{
while(n % i == 0)
n /= i;
return n == 1;
}
}
return 0;
}
void solve(int n)
{
if(n == 2)
{
printf("1\n");
return;
}
if(n == 4)
{
printf("3\n");
return;
}
if(!exist(n))
{
printf("-1\n");
return;
}
ans.clear();
int eu = euler(n);
ac(eu);
sort(rec.begin(), rec.end());
int siz = rec.size();
int mark = -1;
for(int i = 2; i < n; i++)
{
if(power(i, eu, n) != 1) continue;
bool ju = true;
for(int j = 0; j < siz; j++)
{
if(power(i, rec[j], n) == 1)
{
ju = false;
break;
}
}
if(ju)
{
ans.push_back(i);
mark = i;
break;
}
}
if(mark == -1)
{
printf("-1\n");
return;
}
for(int i = 2; i < eu; i++)
{
if(gcd(eu, i) == 1)
{
int tmp = power(mark, i, n);
ans.push_back(tmp);
}
}
sort(ans.begin(), ans.end());
int asize = ans.size();
for(int i = 0; i < asize; i++)
{
if(i == 0)
{
printf("%d", ans[i]);
}
else
printf(" %d", ans[i]);
}
printf("\n");
}
int main()
{
pre();
int n;
while(~scanf("%d", &n))
{
solve(n);
}
}
1000ms+过的,还是很慢的...