1. 程式人生 > >3月12日 二次剩餘(shanks解法)

3月12日 二次剩餘(shanks解法)

演算法原理請wiki:Tonelli–Shanks algorithm,迅速深入理解是不太可能的,與cipola演算法相比,shanks解法更數論一點。
(這個演算法是正常的,但是還是tle)
大概流程是:

R2nmodp
p1=Q2s其中Q是奇數。若s=1,即p3mod4
由尤拉判定直接解出Rnp+14modp對於其他情況,隨機選擇一個z(這個跟cipolla很像),使得(zp)=1,令czQmodp,並令RnQ+12tnQ M=s
(1)若
t1
,R就是一個解,另一個解是p-R。
(2)否則,找一個最小的i,0<i<M,使得t2i1bc2Mi1,RRb,ttb2,cb2,M=i,重複(1)。
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
using namespace std;
#define ll long long
ll QP(ll a, ll b, ll p)
{
    a %=
p; ll tmp = 1; while (b) { if (b & 1)tmp = tmp*a%p; b >>= 1; a = a*a%p; } return tmp; } ll le(ll n, ll p) { return QP(n, (p - 1) >> 1, p); } ll solve(ll n, ll p) { ll tp = p - 1; ll s = 0; while (!(tp & 1)) { s
++; tp >>= 1;//tp 也就是 Q } if (s == 1) { return QP(n, (p + 1) / 4, p); } ll z; while (true) { z = rand() % p; if (le(z, p) + 1 == p) break; } ll c = QP(z, tp, p); ll R = QP(n, (tp + 1) >> 1, p), t = QP(n, tp, p), M = s; while (true) { if (t == 1)return (R%p + p) % p; ll i = 1, j = 2; while (QP(t, j, p) != 1) { i++; j = 1 << j; } ll po = 1; for (int i = 1; i <= M - i - 1; i++)po <<= 1; ll b = QP(c, po, p); // ll b=QP(c,1<<(M-i-1),p); R = R*b%p; t = t*b*b%p; c = b*b%p; M = i; } return 0; } int main() { int num; scanf("%d", &num); for (int i = 0; i < num; i++) { ll n, p; scanf("%lld%lld", &n, &p); if (n == 2) { if (n%p == 1)printf("1\n"); else printf("No root\n"); continue; } if (le(n, p) + 1 == p) { printf("No root\n"); continue; } ll a = solve(n, p); ll b = p - a; if (a > b)std::swap(a, b); if (a == b) { printf("%lld\n", a); } else printf("%lld %lld\n", a, b); } return 0; }