二次剩余 Cipoila 模板 P5491

本文详细介绍了如何使用Cipolla算法求解二次剩余问题,通过构造虚根来找到模p域下的解。文章提供了一种思路和代码实现,并以洛谷P5491题目为例进行说明,展示了Cipolla算法在实际问题中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

本文介绍求解二次剩余的Cipolla模板
求解二次剩余问题一般有两个方向

比较常规的方向是用 Cipolla 算法 或 Legendre 算法 或 Tonelli-Shanks 算法进行计算
这三个算法中Cipolla使用最为广泛, 严谨证明过程可参考oiwiki-Cipolla
Legendre算法记载不详、Tonelli算法用到了原根相关知识
而Cipolla则关键思想是用到了模p域的虚根

另一个方向是利用BSGS大小步算法配合原根解决,可参考oiwiki BSGS进阶篇

一、例题

以洛谷P5491为例 链接: P5491

二、思路及代码

1.思路

我们的问题是求 x 2 ≡ N ( m o d p ) x^2 \equiv N \pmod p x2N(modp)的解,Cipolla方法是基于虚根的构造。我们首先找一个 a 使得 ω 2 = a 2 − N \omega ^2 = a^2 -N ω2=a2N为非二次剩余 ,这样的 a 很好找,[1, p-1]之间有一半是满足的。那么则有: ω p − 1 ≡ ( a 2 − N ) p − 1 2 ≡ − 1 ( m o d p ) \omega ^{p-1} \equiv (a^2 - N)^{\frac{p-1}{2}} \equiv -1 \pmod p ωp1(a2N)2p11(modp)此时的 w 便是我们所构造的虚根,那我们断言: x ≡ ( a + ω ) p + 1 2 ( m o d p ) x \equiv (a + \omega)^{\frac{p+1}{2}} \pmod p x(a+ω)2p+1(modp)即为我们的解,原因如下: x 2 ≡ ( a + w ) p + 1 ≡ ( a + ω ) ( a p + ω p ) ≡ ( a + ω ) ( a − ω ) ≡ ( a 2 − ω 2 ) ≡ N ( m o d p ) x^2 \equiv (a+w)^{p+1} \\\equiv (a+\omega)(a^p+\omega^p)\\ \equiv (a+\omega)(a-\omega) \\\equiv(a^2-\omega^2)\equiv N\pmod p x2(a+w)p+1(a+ω)(ap+ωp)(a+ω)(aω)(a2ω2)N(modp)
现在的问题是我们如何求出这个具体的 x ,方法是利用虚根,即: ( a + b ω ) ∗ ( c + d ω ) = a c + b d ω 2 + ( a d + b c ) ω (a+b\omega)*(c+d\omega) = ac+bd\omega^2 + (ad+bc)\omega (a+bω)(c+dω)=ac+bdω2+(ad+bc)ω我们最后只需取其实部即可,再将 ω 2 = a 2 − N \omega ^2 = a^2-N ω2=a2N代入复数运算即可。

2.代码

代码如下(示例):

#include <iostream>
#define int long long
using namespace std;
int p, ww;
struct complex {
  int x, y;
  complex(int _x = 0, int _y = 0) : x(_x), y(_y) {}
};
complex operator+(complex &a, complex &b) {
  return complex((a.x + b.x) % p, (a.y + b.y) % p);
}
complex operator-(complex &a, complex &b) {
  return complex((a.x - b.x + p) % p, (a.y - b.y + p) % p);
}
complex operator*(complex &a, complex &b) {
  return complex((a.x * b.x % p + a.y * b.y % p * ww % p + p) % p,
                 (a.x * b.y + a.y * b.x) % p);
}  // (a + bw) * (c + dw) = ac + bdww + w(ad + bc)
complex quickPow(complex x, int n) {
  complex ans = complex(1, 0);
  while (n) {
    if (n & 1) ans = ans * x;
    x = x * x;
    n >>= 1;
  }
  return ans;
}
int quickPow(int x, int n) {  // 计算 Legendre
  int ans = 1;
  while (n) {
    if (n & 1) ans = ans * x % p;
    x = x * x % p;
    n >>= 1;
  }
  return ans;
}
int Legendre(int x) {  // 1: 二次剩余, p-1: 非二次剩余
  return quickPow((x % p + p) % p, (p - 1) / 2);
}
complex Cipolla(int n) {
  int a = rand() % p;  // w 为单位根 ww = w^2,  w ^ (p - 1) = -1 (mod p)
  while (Legendre(a * a - n) == 1) a = rand() % p;
  ww = ((a * a - n) % p + p) % p;  // ww = a^2 - n (mod p)
  complex ans = quickPow(complex(a, 1), (p + 1) / 2);
  return ans;  // ans = (a + w) ^ (p + 1) / 2
}
signed main() {
  //   freopen("in.txt", "r", stdin);
  //   freopen("out.txt", "w", stdout);
  int t, n;
  scanf("%lld", &t);
  while (t--) {
    scanf("%lld%lld", &n, &p);
    if (!n) {
      printf("0\n");
      continue;
    }
    if (p == 2) {
      printf("%lld\n", n);
      continue;
    }
    if (Legendre(n) == p - 1) {
      printf("Hola!\n");
      continue;
    }
    complex ans = Cipolla(n);
    int x1 = ans.x;
    int x2 = p - x1;
    if (x1 > x2) swap(x1, x2);
    if (x1 != x2)
      printf("%lld %lld\n", x1, x2);
    else
      printf("%lld\n", x1);
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值