BGV方案
SIMD技術
中國剩餘定理
在《孫子算經》中有這樣一個問題:“今有物不知其數,三三數之剩二(除以3餘2),五五數之剩三(除以5餘3),七七數之剩二(除以7餘2),問物幾何?”這個問題稱為“孫子問題”,該問題的一般解法國際上稱為“中國剩餘定理”。
簡單點說就是求x,使其滿足:
我們的主要求解方法分為三步:
- 找出三個數:從3和5的公倍數中找出被7除餘1的最小數15,從3和7的公倍數中找出被5除餘1 的最小數21,最後從5和7的公倍數中找出除3餘1的最小數70。
- 用15乘以2(2為最終結果除以7的餘數),用21乘以3(3為最終結果除以5的餘數),同理,用70乘以2(2為最終結果除以3的餘數),然後把三個乘積相加(15*2+21*3+70*2)得到和233。
- 用233除以3,5,7三個數的最小公倍數105,得到餘數23,即233%105=23。這個餘數23就是符合條件的最小數。
演算法分析
我們將“孫子問題”拆分成幾個簡單的小問題,從零開始,試圖揣測古人是如何推匯出這個解法的。
首先,我們假設n1是滿足除以3餘2的一個數,比如2,5,8等等,也就是滿足3*k+2(k>=0)的一個任意數。同樣,我們假設n2是滿足除以5餘3的一個數,n3是滿足除以7餘2的一個數。
有了前面的假設,我們先從n1這個角度出發,已知n1滿足除以3餘2,能不能使得 n1+n2 的和仍然滿足除以3餘2?進而使得n1+n2+n3的和仍然滿足除以3餘2?
這就牽涉到一個最基本數學定理,如果有a%b=c,則有(a+kb)%b=c(k為非零整數),換句話說,如果一個除法運算的餘數為c,那麼被除數與k倍的除數相加(或相減)的和(差)再與除數相除,餘數不變。這個是很好證明的。
以此定理為依據,如果n2是3的倍數,n1+n2就依然滿足除以3餘2。同理,如果n3也是3的倍數,那麼n1+n2+n3的和就滿足除以3餘2。這是從n1的角度考慮的,再從n2,n3的角度出發,我們可推匯出以下三點:
- 為使n1+n2+n3的和滿足除以3餘2,n2和n3必須是3的倍數。
- 為使n1+n2+n3的和滿足除以5餘3,n1和n3必須是5的倍數。
- 為使n1+n2+n3的和滿足除以7餘2,n1和n2必須是7的倍數。
因此,為使n1+n2+n3的和作為“孫子問題”的一個最終解,需滿足:
- n1除以3餘2,且是5和7的公倍數。
- n2除以5餘3,且是3和7的公倍數。
- n3除以7餘2,且是3和5的公倍數。
所以,孫子問題解法的本質是從5和7的公倍數中找一個除以3餘2的數n1,從3和7的公倍數中找一個除以5餘3的數n2,從3和5的公倍數中找一個除以7餘2的數n3,再將三個數相加得到解。在求n1,n2,n3時又用了一個小技巧,以n1為例,並非從5和7的公倍數中直接找一個除以3餘2的數,而是先找一個除以3餘1的數,再乘以2。
這裡又有一個數學公式,如果a%b=c,那麼(a*k)%b=a%b+a%b+…+a%b=c+c+…+c=kc(k>0),也就是說,如果一個除法的餘數為c,那麼被除數的k倍與除數相除的餘數為kc。展開式中已證明。
最後,我們還要清楚一點,n1+n2+n3只是問題的一個解,並不是最小的解。如何得到最小解?我們只需要從中最大限度的減掉3,5,7的公倍數105即可。道理就是前面講過的定理“如果a%b=c,則有(a-kb)%b=c”。所以(n1+n2+n3)%105就是最終的最小解。
總結
經過分析發現,中國剩餘定理的孫子解法就是以下兩個基本數學定理的靈活運用:
- 如果 a%b=c , 則有 (a+kb)%b=c (k為非零整數)。
- 如果 a%b=c,那麼 (a*k)%b=kc (k為大於零的整數)。
擴充套件演算法
設正整數兩兩互素,則同餘方程組
有整數解。並且在模下的解是唯一的,解為
其中
,而
為
模
的逆元。
程式碼:
int CRT(int a[],int m[],int n)
{
int M = 1;
int ans = 0;
for(int i=1; i<=n; i++)
M *= m[i];
for(int i=1; i<=n; i++)
{
int x, y;
int Mi = M / m[i];
extend_Euclid(Mi, m[i], x, y);
ans = (ans + Mi * x * a[i]) % M;
}
if(ans < 0) ans += M;
return ans;
}
多項式中國剩餘定理
求乘法逆元
有兩種方法:
1、費馬小定理
該方法速度非常快
求逆元程式碼:
#include <stdio.h>
#include <math.h> int main()
{
int m, n, x;
puts(" 基於費馬定理求逆元\n");
puts(" 對m * x = 1 mod n,求x\n");
printf("請輸入m=");
scanf("%d", &m);
printf("請輸入n=");
scanf("%d", &n);
x = (int)pow(m, n - 2) % n;
printf("x=%d\n", x);
system("pause");
return 0;
}
2、擴充套件歐幾里得
擴充套件歐幾里得演算法實現:
#include<iostream>
using namespace std; //遞迴求解
int exgcd(int a, int b, int& x, int& y)
{
if (b == 0)
{
x = 1;
y = 0;
return a;
}
int gcd = exgcd(b, a % b, x, y);
int x2 = x, y2 = y;
x = y2;
y = x2 - (a / b) * y2;
return gcd;
} //非遞迴求解
int exgcd01(int a, int b, int& x, int& y)
{
int x1, y1, x0, y0;
x0 = 1; y0 = 0;
x1 = 0; y1 = 1;
x = 0; y = 1;
int r = a % b;
int q = (a - r) / b;
while (r)
{
x = x0 - q * x1; y = y0 - q * y1;
x0 = x1; y0 = y1;
x1 = x; y1 = y;
a = b; b = r; r = a % b;
q = (a - r) / b;
}
return b;
} int main()
{
int x, y, a, b,option;
cout << "擴充套件歐幾里得演算法" << endl;
cout << endl << "請選擇:1、遞迴求解;2、非遞迴求解" << endl;
cin >> option;
if (option == 1)
{
cout << "請輸入a和b:" << endl;
cin >> a >> b;
cout << "a和b的最大公約數:" << endl;
cout << exgcd(a, b, x, y) << endl;
cout << "ax+by=gcd(a,b) 的一組解是:" << endl;
cout << x << " " << y << endl;
}
else if (option == 2)
{
cout << "請輸入a和b:" << endl;
cin >> a >> b;
cout << "a和b的最大公約數:" << endl;
cout << exgcd01(a, b, x, y) << endl;
cout << "ax+by=gcd(a,b) 的一組解是:" << endl;
cout << x << " " << y << endl;
}
else
cout << "請重新輸入!" << endl;
return 0;
}
求逆元演算法實現:請參考 求逆元
參考
1、雲外包密文查詢和計算研究-全韓彧
4、演算法學習 之 歐幾里得演算法和擴充套件歐幾里得演算法(二)
6、多項式的 “中國剩餘定理”-包志超