8.12.18 ACM-ICPC数学 数论 二次剩余

8.12.18 ACM-ICPC数学 数论 二次剩余

在数论中,二次剩余是一个非常重要的概念,它在密码学和数论算法中有着广泛的应用。在本节中,我们将详细介绍二次剩余的定义、性质,以及如何使用C++进行实现,并通过例题洛谷 P5491 来进行应用。

二次剩余的定义

对于一个给定的整数𝑛n和一个模𝑝p,如果存在整数𝑥x,使得:

𝑥2≡𝑛 (mod 𝑝)x2≡n (mod p)

那么称𝑛n是模𝑝p的一个二次剩余;否则称𝑛n是模𝑝p的一个二次非剩余。

简单来说,如果存在某个整数𝑥x的平方与𝑛n同余于模𝑝p,那么𝑛n就是模𝑝p的一个二次剩余。

二次剩余的性质

  1. 存在性:对于一个质数𝑝p,模𝑝p的二次剩余数量为𝑝−122p−1​,二次非剩余的数量也为𝑝−122p−1​。

  2. 乘法闭包:如果𝑎a和𝑏b都是模𝑝p的二次剩余,那么𝑎𝑏ab也是模𝑝p的二次剩余。

  3. 乘法反闭包:如果𝑎a是模𝑝p的二次剩余,而𝑏b是模𝑝p的二次非剩余,那么𝑎𝑏ab是模𝑝p的二次非剩余。

二次剩余的求解算法

求解一个数是否为二次剩余及其对应的平方根有多种方法,其中一种常见的方法是使用Tonelli-Shanks算法。该算法在模数为奇质数时,可以高效地求解二次剩余。

Tonelli-Shanks算法简介

Tonelli-Shanks算法用于求解方程𝑥2≡𝑛 (mod 𝑝)x2≡n (mod p),其中𝑝p是奇质数。算法步骤如下:

  1. 将𝑝−1p−1分解为𝑞⋅2𝑠q⋅2s,其中𝑞q为奇数。
  2. 找到一个模𝑝p的非二次剩余𝑧z。
  3. 设置初始值𝑚=𝑠m=s,𝑐=𝑧𝑞c=zq,𝑡=𝑛𝑞t=nq,𝑟=𝑛(𝑞+1)/2r=n(q+1)/2。
  4. 重复以下步骤直到𝑡≡1 (mod 𝑝)t≡1 (mod p):
    • 找到最小的非负整数𝑖i,使得𝑡2𝑖≡1 (mod 𝑝)t2i≡1 (mod p)。
    • 更新𝑏=𝑐2𝑚−𝑖−1b=c2m−i−1,然后设置𝑚=𝑖m=i,𝑐=𝑏2c=b2,𝑡=𝑡⋅𝑐t=t⋅c,𝑟=𝑟⋅𝑏r=r⋅b。

最终,𝑟r即为方程的一个解。

C++实现示例

以下是Tonelli-Shanks算法的C++实现代码:

#include <iostream>
#include <vector>
#include <cassert>
using namespace std;

// 快速幂
long long power(long long a, long long b, long long p) {
    long long res = 1;
    while (b) {
        if (b & 1) res = res * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return res;
}

// 判断n是否为模p的二次剩余,p是奇质数
bool is_quadratic_residue(long long n, long long p) {
    return power(n, (p - 1) / 2, p) == 1;
}

// Tonelli-Shanks算法求解x^2 ≡ n (mod p),p是奇质数
long long tonelli_shanks(long long n, long long p) {
    if (n == 0) return 0;
    if (p == 2) return n;
    if (!is_quadratic_residue(n, p)) return -1;  // n不是二次剩余,无解

    long long q = p - 1;
    long long s = 0;
    while (q % 2 == 0) {
        q /= 2;
        ++s;
    }
    if (s == 1) return power(n, (p + 1) / 4, p);

    long long z;
    for (z = 2; z < p; ++z) {
        if (!is_quadratic_residue(z, p)) break;
    }

    long long m = s;
    long long c = power(z, q, p);
    long long t = power(n, q, p);
    long long r = power(n, (q + 1) / 2, p);

    while (t != 0 && t != 1) {
        long long t2 = t;
        long long i = 0;
        for (; t2 != 1; ++i) {
            t2 = t2 * t2 % p;
        }
        long long b = power(c, 1LL << (m - i - 1), p);
        m = i;
        c = b * b % p;
        t = t * c % p;
        r = r * b % p;
    }

    return r;
}

int main() {
    long long n, p;
    cout << "请输入n和p(奇质数):" << endl;
    cin >> n >> p;
    long long res = tonelli_shanks(n, p);
    if (res == -1) {
        cout << n << " 不是模 " << p << " 的二次剩余" << endl;
    } else {
        cout << n << " 模 " << p << " 的一个平方根是 " << res << endl;
    }
    return 0;
}

例题:洛谷 P5491【模板】二次剩余

题目要求求解一个数在模质数的情况下是否为二次剩余,并求出其平方根。以下是具体题目描述和代码实现:

题目描述:给定一个整数𝑛n和一个质数𝑝p,判断𝑛n是否是模𝑝p的二次剩余,并输出一个平方根。

代码实现

#include <iostream>
using namespace std;

long long power(long long a, long long b, long long p) {
    long long res = 1;
    while (b) {
        if (b & 1) res = res * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return res;
}

bool is_quadratic_residue(long long n, long long p) {
    return power(n, (p - 1) / 2, p) == 1;
}

long long tonelli_shanks(long long n, long long p) {
    if (n == 0) return 0;
    if (p == 2) return n;
    if (!is_quadratic_residue(n, p)) return -1;

    long long q = p - 1;
    long long s = 0;
    while (q % 2 == 0) {
        q /= 2;
        ++s;
    }
    if (s == 1) return power(n, (p + 1) / 4, p);

    long long z;
    for (z = 2; z < p; ++z) {
        if (!is_quadratic_residue(z, p)) break;
    }

    long long m = s;
    long long c = power(z, q, p);
    long long t = power(n, q, p);
    long long r = power(n, (q + 1) / 2, p);

    while (t != 0 && t != 1) {
        long long t2 = t;
        long long i = 0;
        for (; t2 != 1; ++i) {
            t2 = t2 * t2 % p;
        }
        long long b = power(c, 1LL << (m - i - 1), p);
        m = i;
        c = b * b % p;
        t = t * c % p;
        r = r * b % p;
    }

    return r;
}

int main() {
    long long n, p;
    cin >> n >> p;
    long long res = tonelli_shanks(n, p);
    if (res == -1) {
        cout << "Hola!" << endl;  // 二次非剩余
    } else {
        cout << res << endl;  // 输出平方根
    }
    return 0;
}

以上代码实现了洛谷 P5491 题目的求解过程,通过Tonelli-Shanks算法判断一个数是否为二次剩余并求出其平方根。如果数不是二次剩余,输出"Hola!",否则输出平方根。

  • 18
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

夏驰和徐策

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

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

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

打赏作者

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

抵扣说明:

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

余额充值