題意:
給定兩個數字在模1e9 + 7意義下x + y的和以及x和y的乘積,求x和y
題目分析
由於x和y的取值範圍,我們可以得到x + y的範圍爲 0 <= x + y < 2 * p,那麼對於第一個式子我們可以得到: x + y = b 或者 x + y = p + b的兩種情況.
對於第一種情況,將第一個式子代入第二個式子可得
y * (b - y) = k * p + c
移項得,y ^ 2 - b * y + k * p + c = 0
如果y要有整數解,當且僅當 b ^ b - 4 * (k * p + c) 爲完全平方數,從而得到如下方程
a ^ 2 = b * b - 4 * (k * p + c)
對兩邊同時模p可得
a ^ 2 ≡b * b - 4 * c (mod p)
從而轉化爲一個二次剩餘的問題
二次剩餘:
當時比賽的時候用下面的博客魔改了一下過了,博客裏邊有簡單的證明
https://blog.csdn.net/acdreamers/article/details/10182281
在比賽之後又參考了下邊的博客,才真正理解了這個神奇的東西
https://blog.csdn.net/a_crazy_czy/article/details/51959546
Part one 二次剩餘的基本概念
在數論中,特別在同餘理論裏,一個整數 對另一個整數 的二次剩餘(英語:Quadratic residue)指X的平方 除以得到的餘數。
當存在某個 , 式子成立時,稱 “ 是模 的二次剩餘 ”
當對任意 ,上式都不成立時,稱 “ 是模 的二次非剩餘 ”
關於二次剩餘的判定,可以用歐拉準則來判斷,具體方法如下:
若 p 是奇質數且 p 不能整除 d ,則:
d 是模 p 的二次剩餘當且僅當:
d 是模 p 的二次非剩餘當且僅當:
以勒讓德符號表示,即爲:
其中勒讓德符號的定義如下:
歐拉從充分性和必要性兩個方面證明了上述結論,給出的證明如下:
首先,由於是一個奇素數,由費馬小定理, 。但是 是一個偶數,
所以有
是一個素數,所以 和 中必有一個是 的倍數。因此 模 的餘數必然是或。
必要性:
證明若 是模 的二次剩餘,則
若 是模 的二次剩餘,則存在 ,跟 互質。根據費馬小定理得:
充分性:
證明若 ,則 是模 的二次剩餘是一個奇素數,所以關於 的原根存在。設 是 的一個原根,
則存在 ,使得 。
於是
是的一個原根,因此 模 的指數是 ,於是 整除 。這說明 是一個偶數。令 ,就有 。 是模 的二次剩餘。
Part two 二次剩餘的問題求解(隨機大法好!)
問題描述:
對於給定的 和 判斷 是否是 的二次剩餘,
如果是,則輸出的一個解,
其中, 是奇素數。
題目傳送門:http://acm.timus.ru/problem.aspx?space=1&num=1132
引理:
對於有個解
證明如下:
設是的兩個解,我們可以得到
進一步化簡得到,由的範圍我們可以指導,
,並且,所以有個解。
Cipolla算法
爲了求出的解,我們通過隨機數找到一個滿足爲-1的根據引理我們可以知道這樣的一共有個,因此求出這樣的的期望次數爲約爲,基本上我們隨機兩次就可以得到這樣的,一般我們會隨機比較多的次數防止自己臉黑。
在的得到這樣的一個之後我們做如下的處理:
①取爲基本的虛數單位,設爲,類比作爲
②構造一個與當前虛數單位所對應的數域,類比於實數到複數,則在這個數域中所有的數字都可以表示爲的形式
③驗證當前數域的運算的封閉性、交換律、結合律、分配律,即驗證它是一個合法的數域
對於這個數域,我們可以得到如下的結論:
①
證明:根據爲我們可以知道,,從而我們知道
②
證明見我之前寫的題解:https://blog.csdn.net/z472421519/article/details/99842319
我們取
我們可以得到
利用剛纔的結論
再利用費馬小定理 得到,
代入
,即滿足之前的二次剩餘方程
代碼
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <iostream>
#include <math.h>
using namespace std;
typedef long long LL;
LL quick_mod(LL a, LL b, LL m)
{
LL ans = 1;
a %= m;
while(b)
{
if(b & 1)
{
ans = ans * a % m;
b--;
}
b >>= 1;
a = a * a % m;
}
return ans;
}
struct T
{
LL p, d;
};
LL w;
//二次域乘法
T multi_er(T a, T b, LL m)
{
T ans;
ans.p = (a.p * b.p % m + a.d * b.d % m * w % m) % m;
ans.d = (a.p * b.d % m + a.d * b.p % m) % m;
return ans;
}
//二次域上快速冪
T power(T a, LL b, LL m)
{
T ans;
ans.p = 1;
ans.d = 0;
while(b)
{
if(b & 1)
{
ans = multi_er(ans, a, m);
b--;
}
b >>= 1;
a = multi_er(a, a, m);
}
return ans;
}
//求勒讓德符號
LL Legendre(LL a, LL p)
{
return quick_mod(a, (p-1)>>1, p);
}
LL mod(LL a, LL m)
{
a %= m;
if(a < 0) a += m;
return a;
}
LL Solve(LL n,LL p)
{
if(p == 2) return 1;
if (Legendre(n, p) + 1 == p)
return -1;
LL a = -1, t;
while(true)
{
a = rand() % p;
t = a * a - n;
w = mod(t, p);
if(Legendre(w, p) + 1 == p) break;
}
T tmp;
tmp.p = a;
tmp.d = 1;
T ans = power(tmp, (p + 1)>>1, p);
return ans.p;
}
int main()
{
LL t;
scanf("%d", &t);
while(t--)
{
LL n,p,N;
LL x,y;
scanf("%lld%lld",&x,&y);
p = 1e9 + 7;
n = x * x - 4 * y;
N = (x + p) * (x + p) - 4 * y;
// //printf("%lld",n);
if(n == 0)
{
printf("%lld %lld\n",x / 2,x / 2);
continue;
}
LL a;
LL X,Y;
bool suc = false;
for(int i = 1;i <= 10;i++)
{
if(n > 0)
{
a = Solve(n, p);
X = (x - a) / 2,Y = (x + a) / 2;
if(((X + Y) % p == (x % p) && (X * Y) % p == y) && (X >= 0 && X <= p) && (Y >= 0 && Y <= p))
{
suc = true;
break;
}
}
if(N > 0)
{
a = Solve(N, p);
X = (x + p - a) / 2,Y = (x + p + a) / 2;
if(((X + Y) % p == (x % p) && (X * Y) % p == y) && (X >= 0 && X <= p) && (Y >= 0 && Y <= p))
{
suc = true;
break;
}
}
}
if(suc)
printf("%lld %lld\n",X,Y);
else
printf("-1 -1\n");
}
return 0;
}
不知道爲啥有的時候會出錯,考慮到隨機算法的臉黑性,我加了個暴力判斷,在運算超過十次之後就直接輸出