CCF-GESP计算机学会等级考试2025年3月五级C++T2 原根判断

P11961 [GESP202503 五级] 原根判断

题目背景

截止 2025 年 3 月,本题可能超出了 GESP 考纲范围。在该时间点下,原根是 NOI 大纲 8 级知识点(NOI 级),而相对简单的无需原根知识的做法中,使用的费马小定理与欧拉定理也属于 NOI 大纲 7 级知识点(提高级),且均未写明于 GESP 大纲中。需要注意,GESP 大纲和 NOI 大纲是不同的大纲。

若对题目中原根这一概念感兴趣,可以学习完成 【模板】原根

题目描述

小 A 知道,对于质数 p p p 而言, p p p 的原根 g g g 是满足以下条件的正整数:

  • 1 < g < p 1<g<p 1<g<p
  • g p − 1   m o d   p = 1 g^{p-1}\bmod{p}=1 gp1modp=1
  • 对于任意 1 ≤ i < p − 1 1\le i<p-1 1i<p1 均有 g i   m o d   p ≠ 1 g^i\bmod{p}\neq1 gimodp=1

其中 a   m o d   p a\bmod{p} amodp 表示 a a a 除以 p p p 的余数。

小 A 现在有一个整数 a a a,请你帮他判断 a a a 是不是 p p p 的原根。

输入格式

第一行,一个正整数 T T T,表示测试数据组数。

每组测试数据包含一行,两个正整数 a , p a,p a,p

输出格式

对于每组测试数据,输出一行,如果 a a a p p p 的原根则输出 Yes,否则输出 No

输入输出样例 #1

输入 #1

3
3 998244353
5 998244353
7 998244353

输出 #1

Yes
Yes
No

说明/提示

数据范围

对于 40 % 40\% 40% 的测试点,保证 3 ≤ p ≤ 1 0 3 3\le p\le10^3 3p103

对于所有测试点,保证 1 ≤ T ≤ 20 1\le T\le20 1T20 3 ≤ p ≤ 1 0 9 3\le p\le10^9 3p109 1 < a < p 1<a<p 1<a<p p p p 为质数。

解析

感谢 冯世龙老师 提供的题解,可以在不学习原根的情况下解题,详见代码:

#include <iostream>
using namespace std;

// 已知
int t, a, p;
// 未知

int fpq(int a, int b)
{
    if (b == 0) return 1;
    int res = fpq(a, b / 2); // 快速幂
    res = 1L * res * res % p; // 取模
    if (b & 1) res = 1L * res * a % p; // b如果是奇数再取模一次
    return res;
}

bool check()
{
    if (fpq(a, p - 1) != 1) return false; // 判断性质2
    // 然后去判断性质3,在没达到p之前找是否有一个数使得a的(p-1)/i次方模p等于1
    // 如果有,那么a不是原根,否则是原根,这是基于a^x=(a^p1)^p2,p1*p2=p-1
    int phi = p - 1, r = phi; // 利用快速幂分解质因数
    for (int i = 2; i * i <= p; i++)
    {
        if (phi % i == 0) // 找phi的因子,所有因子都会找到
        {
            // 在p-1之前就有一个数可以使得a的phi次方模p等于1,判断性质3
            if (fpq(a, phi / i) == 1) return false;
            while (r % i == 0) r /= i; // 对r分解质因数
        }
    } 
    // 还有其他质因子 就得再判断一次
    if (r != 1 and fpq(a, r) == 1) return false; // 判断性质2
    return true;
}

int main()
{
    cin >> t;
    for (int i = 0; i < t; i++)
    {
        cin >> a >> p;
        if (check()) cout << "Yes" << endl;
        else cout << "No" << endl;
    }
    return 0;
}

官方标程:

#include <cstdio>
using namespace std;
int a, p;
int ans;
int fpw(int b, int e) {
    if (e == 0)
        return 1;
    int r = fpw(b, e >> 1);
    r = 1ll * r * r % p;
    if (e & 1)
        r = 1ll * r * b % p;
    return r;
}
void check(int e) {
    if (fpw(a, e) == 1)
        ans = 0;
}
int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &a, &p);
        ans = 1;
        int phi = p - 1, r = phi;
        for (int i = 2; i * i <= phi; i++)
            if (phi % i == 0) {
                check(phi / i);
                while (r % i == 0)
                    r /= i;
            }
        if (r > 1)
            check(phi / r);
        printf(ans ? "Yes\n" : "No\n");
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长春高老师信奥工作室

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值