學習總結:初等數論(3)——原根、指標及其應用
最近知道了一本書叫《數論概論(第3版)》(A Friendly Introduction to Number Theory),簡單翻了翻,感覺這本書寫的非常好。想起以前剛接觸數論時,沒有看這本書來入門,真是十分遺憾。這本書面向非數學專業讀者,語言生動幽默而不失嚴謹性,注重對知識的感性認識(用了大量的例子),和對數學思想和方法的滲透(比如:大膽猜想規律,證明時用“計數”法等)。這里強烈推薦這本書!
下面總結一下這幾天學習的內容。
一、整數的階
a與n是互質的兩個數。可以發現,總存在某個數x,滿足 ax ≡ 1(mod n)。
使ax ≡ 1(mod n)滿足的最小的正整數x叫做a模n的階(或次數),記做ena(有的地方記做ordna)。
怎么理解呢?你要不斷計算a,a2,a3,…,(注意要模n)我們知道,根據鴿巢原理,一定會有循環。而a和n互質時,總會在某個時刻出現了1,下一個時刻又是a,……於是就有了循環。這個出現1的最小的指數,叫做a的階。
比如:我們要找3模7的階,計算31,32,…,36模7的值依次為:
3,2,6,4,5,1,
所以我們得到3模7的階為6。
a與n互質時,根據歐拉定理有a?(n)≡1(mod n)。這么說來,剛才我們不斷計算a的冪的循環節的長度(即a的階)“擴增”若干次后應該為?(n)。所以,
ena | ?(n)。
同樣的道理,為了使方程 ax≡1(mod n)有解,應該把循環節長度“擴增”若干次后得到x,所以
ena | x
二、原根
設a與n是互質的整數,當a模n的階為?(n)時,把a叫做n的原根。
怎么理解呢?我們計算a的冪的序列,第一次得到1的時候,得到了完整的一個循環節。我們上面已經說了,?(n)一定是(a模n的階)的倍數。如果這個循環節的長度恰好就是?(n)(循環節的長度已經達到最大了),那么a就是n的原根。
(1)原根的存在性
一個數可能有很多個原根,也可能沒有原根。可以證明:
正整數n存在原根,當且僅當
n = 2, 4, pt或2pt(其中p是奇素數,t是正整數)。
舉例:5的原根有2和3;7的原根有3和5。
(2)原根的一個性質
原根為什么這么重要?
設g是n的一個原根,那么:g,g2,g3,…,g?(n)(即g0≡1(mod n))一定兩兩不同。(如果有相同,比如說存在0≤i<j<?(n)滿足gi≡gj(mod n),那么就會有gj-i≡1(mod n),而j – i<?(n),這時?(n)就不是g的階了)
看看方程ax≡1(mod n),如果x是存在的,那么a一定是與n互質的數(如果a和n有某個大於1的公因子,無論a怎么取冪再模n,這個公因子都“消不掉”,不會有ax mod n = 1)。所以“合法”的a有?(n)個。而我們找到一個原根g后,g的冪(g,g2,g3,…,g?(n))一定就是這些合法的a(即與n互質的?(n)個數。因為g與n互質,g的冪也與n互質,而這些冪又兩兩不同,一定能把“與n互質的數”取盡)。
一句話總結上面的內容:對於n的一個原根g,滿足
{ 1,g,g2,g3,…,g?(n) – 1 } = { x | x與n互質,1≤x<n }
特別地,對於奇質數p的一個原根g,滿足
{ 1,g,g2,g3,…,gp – 2 } = { 1, 2, 3, …, p – 1 }
(3)原根的個數
一個數n,如果存在原根,就一定?(?(n))個。怎么證明呢?為此我們先討論下面這個問題:
已知a模n的階ena,怎么求au的模n的階?
設au模n的階為t,那么t應該是最小的正整數,滿足(au)t≡aut≡1 (mod n)。
由之前的討論,可以知道:u×t應該是ena的倍數,並且t最小。
由數論的基本知識可以得出,enau = t = ena / gcd(u, ena)。
如果我們找出一個原根g,怎么得出其他的原根?
首先,其他的原根一定是g的若干次冪。
哪些“g的若干次冪”可以成為原根?
用原根的定義!
對於某個“g的若干次冪”,比如gi (0≤i<?(n)),由上面的結論得出,它模n的階為
?(n) / gcd(i, ?(n))
欲使它的階也為?(n),即
?(n) / gcd(i, ?(n)) = ?(n)
需要使gcd(i, ?(n)) = 1,也就是i和?(n)互質。
有多少個i滿足它和和?(n)互質?換句話說,在0 ~ ?(n)–1的數中,有多少個與?(n)互質?答案應該是?(?(n))。所以,一個數如果有原根,那么它有?(?(n))個原根。
下面舉個例子:
7的原根有?(?(7)) = ?(6) = 2個,最小的一個是3。
在32,33,…,36中,哪個指數與6互質?只有5,所以另一個原根是35≡5(mod 7)。
怎么找原根?
通常最小的原根都比較小,所以暴力從1開始枚舉就可以了。判斷一個數a是不是n的原根,需要判斷?(n)是否是a的階,直接的判斷方法是枚舉?(n)的每一個因子d(除去它本身),判斷是否ad≡1(mod n)。但這樣做了很多重復的判斷。
比如我們要判斷a模n=37的階是否為36,那么只需找出36的兩個質因子2和3,只需判斷36/2和36/3作為a的冪的指數時,(a的冪) mod n是否為1。如果a的階為36/4,36/9,36/6,36/12,…,其實情況已經包含在上面的判斷中了。(比如,如果a36/9≡a4≡1(mod n),那么一定也會滿足a36/2≡3618≡1(modn))。
三、指標(離散對數)
求原根有什么作用?為了計算指標!
對於n的一個原根g,滿足
{ 1,g,g2,g3,…,g?(n) – 1 } = { x | x與n互質,1≤x<n }
再具體一些,可以定義一種“求冪”的運算,這種運算揭示了兩個集合的一一對應關系:
i → gi (1≤i≤?(n))
為什么是一一對應關系?因為gi一定兩兩不同。這一點已經討論過了。
那么,是否有一種“求冪”運算的逆運算?有!
求出n的一個原根g,知道了某個與n互質的數a,n是g的多少次方?
我們用I(a)來表示這個“次方”數,叫做以g為底a模n的指標。(有的地方記做indga)
也就是,依照定義,應該有gI(a)≡a(mod n)(a與n互質)。
顯然,指標的范圍是0≤I(a)<?(n),當指標超過?(n)時,出現了循環,可以把指標mod ?(n)進行簡化。
有點像我們以前學過的對數?(不嚴謹的說,有點像logga?)也許這就是為什么指標也叫離散對數了。
當n為質數時,原根g一定存在,而且?(n) = n – 1,這樣,每個在1~n–1范圍內的數都有指標!
我們可以根據冪的運算法則,對應得出指標的運算法則:
(1) I(ab) = I(a) + I(b) (mod ?(n)) (類比:logaNM = logaN + logbM)
(2) I(ak) = k×I(a) (類比:logaNM = M×logaN)
指標把乘法變加法,把冪變乘法,這一點與對數的運算法則多么相似!
如果有一個指標表,我們可以十分簡便地進行運算。舉例:
n = 37,它的一個原根是a = 2。

要計算23×19 mod 17的值,可以計算
I(23×19) ≡ I(23) + I(19) ≡ 15 + 35 ≡ 50 ≡ 14 (mod 36)
然后,查表可以得出,指標為14的數是30,就是要求的答案了。
很麻煩?
再看一個例子:
I(2914) ≡ 14 × I(29) ≡ 294 ≡ 6 (mod 36)
由表得:I(27) = 6,所以2914 ≡ 27 (mod 37)
你也許會說:有快速冪!在前兩個例子中,似乎指標的優勢沒有體現出來。不過,在解方程的時候,指標就很有用了。
解同余式:


擴展歐幾里得?好像也能解。不過,像下面這樣的同余式呢?

只能用指標來解。兩邊同時求離散對數:

可以解出:


事實上,最后一個例子是指標最重要的運用之一。等會兒我們會詳細討論。
但是,之前的計算都是在指標表已經給了的情況下進行的。沒有指標表怎么辦呢?如何求某個數指標(離散對數)?
更准確地說,給出g, a, p,如何求gk ≡ a(mod p)的最小的k?為了簡化問題,這里規定p為質數。
這里使用一種叫做大步小步(gaint-step baby-step)的算法。算法的核心思想是分塊。取m = [ sqrt(p – 1) ] + 1,然后把k表示成xm + y (0 ≤ y < m)的形式。這樣,x和y的范圍都是0~m(y不含m)。於是gk ≡ (gm)x × gy,可以求出所有的gy(m個取值);然后枚舉x,計算出(gm)x,查找:是否存在某個gy滿足(gm)x × gy ≡ a(mod p)?也就是說:
是否存在某個gy,滿足gy ≡ a × (gmx) –1 (mod p)?
用費馬小定理和快速冪求出逆元(gmx) –1,然后求出a × (gmx) –1。檢查是否有“匹配”的gy。如果我們先把gy放在一個哈希表(或者C++的map)中,那么這一步的查詢就是O(1)(或O(log2m)=O(log2p))的。算法的核心步驟仍然是枚舉,但分塊使時間復雜度變成O(sqrt(P) × log2P)(注意算上求逆元的時間)。
四、N次剩余
這里要解決一個這樣的問題:
給出N, a, p,求滿足xN ≡ a (mod p)(p為質數)的所有解x。
可以形象地理解成求a在模p意義下的N次方根。
剛才我們已經借助例子,初步了解了做法:
xN ≡ a (mod p),找出p的一個原根g,用“大步小步”算法求出以g為底a模p的指標I(a)。
同余式變成:
N × I(x) ≡ a (mod p – 1)
由一次同余方程的知識可以知道,有解的條件是
gcd(N, p – 1) | a
而且,解有gcd(N, p – 1)個。
解出所有的可能的I(x),那么x = gI(x)。這些x中重復的要去掉。
代碼:
| #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <vector> #include <map> using namespace std;
typedef long long LL;
int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }
void _gcd(int a, int b, LL &x, LL &y) { if (b == 0) { x = 1; y = 0; return ; } _gcd(b, a%b, y, x); y -= (a/b) * x; }
int extend_gcd(int a, int b, int c, LL &x, LL &y, int &dx, int &dy) { int g = gcd(a, b); if (c % g) return 0; _gcd(a, b, x, y); x *= (c/g); y *= (c/g); dx = b / g; dy = a / g; return g; }
// Ax = B (mod N) // 設Ax = -yN + B // 則Ax + Ny = B bool line_mod_equ(int A, int B, int N, int &x, int &k) { LL x0, y0; int dx, dy; if (!extend_gcd(A, N, B, x0, y0, dx, dy)) return false; x0 %= dx; if (x0 < 0) x0 += dx; x = (int)x0; k = dx; return true; }
LL pow_mod(LL a, LL b, LL p) { if (b == 0) return 1; LL tmp = pow_mod(a, (b>>1), p); if (b & 1) return tmp * tmp % p * a % p; else return tmp * tmp % p; }
//分解質因數 void factor(int x, vector<int> &divs) { divs.clear(); for (int i = 2; i * i <= x; ++ i) if (x % i == 0) { divs.push_back(i); while (x % i == 0) x /= i; } if (x > 1) divs.push_back(x); }
bool g_test(int g, vector<int> &divs, int P) { for (int i = 0; i < (int) divs.size(); ++ i) if (pow_mod(g, (P-1) / divs[i], P) == 1) return false; return true; }
//找原根,p為質數,保證有原根 int primitive_root(int P) { static vector<int> divs; factor(P-1, divs); int g = 1; while (!g_test(g, divs, P)) ++ g; return g; }
// 求解在模P意義下,以a為底N的離散對數b (P為質數) // 即 a ^ b = N (mod P) // 大步小步算法(分塊) // 取s = sqrt(P), 設b = x * s + y // 則 a ^ (x*s + y) = (a^s)^x * a^y = N (mod P) // 求出y = 0~s-1時, a^y的取值;然后枚舉s,算出(a^s)^x,查找是否有匹配的y int discrete_log(int a, int N, int P) { map<int, int> rec; int s = (int)sqrt(P + 0.5); while (s * s <= P) ++ s; LL cur = 1; for (int y = 0; y < s; ++ y) { rec[ cur ] = y; cur = cur * a % P; } LL a_s = cur; // a^s cur = 1; for (int x = 0; x < s; ++ x) { LL a_y = pow_mod(cur, P-2, P) * LL(N) % P; map<int,int> :: iterator it = rec.find( a_y ); if (it != rec.end()) return x * s + it -> second; cur = cur * a_s % P; } return -1; }
// x ^ K = A (mod P) (where P is a prime) // 找p的一個原根g,求出指標 // K I(x) = I(A) (mod P-1) // 有解的條件 gcd( I(x), P-1 ) | I(A) void discrete_root(int K, int A, int P, vector<int> &x) { x.clear(); if (A == 0) { x.push_back(0); return ; } int g = primitive_root(P); int IA = discrete_log(g, A, P); int Ix, delta; if (!line_mod_equ(K, IA, P-1, Ix, delta)) return ; while (Ix < P) { x.push_back( pow_mod(g, Ix, P) ); Ix += delta; } sort(x.begin(), x.end()); x.erase(unique(x.begin(), x.end()), x.end()); }
int main() { int P, K, A; scanf("%d%d%d", &P, &K, &A); static vector<int> x; discrete_root(K, A, P, x); printf("%u\n", x.size()); for (int i = 0; i < (int) x.size(); ++ i) printf("%d\n", x[i]); return 0; }
|
2016-09-06 20:59:38
