【轉】初等數論 ——原根、指標及其應用



轉自:http://blog.163.com/gc_chdch@126/blog/static/172279052201641935828402/

 


 

學習總結:初等數論(3)——原根、指標及其應用  

2016-05-19 15:58:28|  分類: 信息學——學習總 |  標簽:初等數論  數學  |舉報|字號 訂閱

 
 

學習總結:初等數論(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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM