洛谷P11961 [GESP202503 五级] 原根判断
原题
题目背景
截止 2025 年 3 月,本题可能超出了 GESP 考纲范围。在该时间点下,原根是 NOI 大纲 8 级知识点(NOI 级),而相对简单的无需原根知识的做法中,使用的费马小定理与欧拉定理也属于 NOI 大纲 7 级知识点(提高级),且均未写明于 GESP 大纲中。需要注意,GESP 大纲和 NOI 大纲是不同的大纲。
若对题目中原根这一概念感兴趣,可以学习完成 【模板】原根。
题目描述
小 A 知道,对于质数 \(p\) 而言,\(p\) 的原根 \(g\) 是满足以下条件的正整数:
- \(1<g<p\);
- \(g^{p-1}\bmod{p}=1\);
- 对于任意 \(1\le i<p-1\) 均有 \(g^i\bmod{p}\neq1\)。
其中 \(a\bmod{p}\) 表示 \(a\) 除以 \(p\) 的余数。
小 A 现在有一个整数 \(a\),请你帮他判断 \(a\) 是不是 \(p\) 的原根。
输入格式
第一行,一个正整数 \(T\),表示测试数据组数。
每组测试数据包含一行,两个正整数 \(a,p\)。
输出格式
对于每组测试数据,输出一行,如果 \(a\) 是 \(p\) 的原根则输出 Yes
,否则输出 No
。
输入输出样例 #1
输入 #1
3
3 998244353
5 998244353
7 998244353
输出 #1
Yes
Yes
No
说明/提示
数据范围
对于 \(40\%\) 的测试点,保证 \(3\le p\le10^3\)。
对于所有测试点,保证 \(1\le T\le20\),\(3\le p\le10^9\),\(1<a<p\),\(p\) 为质数。
整理&思路
十分明显,这是一道原根题目。首先我们需要写一个快速幂,以备不时之需:
int fpow(int a, int b, int p) {
int res = 1;
while(b) {
if(b&1) res = res*a%p;
a=a*a%p;
b>>=1;
}
return res;
}
很明显,这是一个分治思想。当 \(b\) 为奇数时,将 \(a\) 乘到答案中,然后 \(a\) 平方,\(b\) 右移一位。当 \(b\) 为偶数时,\(a\) 平方,\(b\) 右移一位。这样,我们就可以快速求出 \(a^b\bmod{p}\)。
然后我们需要做一个获取因数的方法:
vector<int> fac;
for(int i=2;i<=p;i++) {
if(p%i==0) {
fac.push_back(i);
if(i*i!=p) fac.push_back(p/i);
}
}
此时我们就可以通过分解(p-1)的方式获取因数,进而通过快速幂以此证明原根(因为如果因数的%是1,那么原根一定不是它,反之亦然 )。
然而,很明显的,我们所采取的是\(p\)的因数而不是\(p-1\)的因数,所以我们需要这样一个骚操作:
p--;
/*...*/
p++;
完美awa
接下来就是完整代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int T;
// p的原根是a,则:
// 1 < a < p
// a^(p-1) mod p = 1
// 对于任意1 <= i < p-1均有a^i mod p != 1
// 快速幂
int fpow(int a, int b, int p) {
int res = 1;
while (b) {
if (b & 1) res = res * a % p;
a = a * a % p;
b >>= 1;
}
return res;
}
signed main() {
scanf("%lld", &T);
while (T--) {
int a, p;
scanf("%lld%lld", &a, &p);
vector<int> fac;
p--;
for(int i=2;i*i<=p;i++) {
if(p%i==0) {
fac.push_back(i);
if(i*i!=p) fac.push_back(p/i);
}
}
p++;
if(fpow(a, p-1, p) != 1)
goto end;
for(auto x : fac) {
if(fpow(a, x, p) == 1)
goto end;
}
puts("Yes");
continue;
end:
puts("No");
}
return 0;
}
恭喜AC!