zoukankan      html  css  js  c++  java
  • 【转载】学习总结:初等数论(3)——原根、指标及其应用

    写得太好了。。忍不住转载啊。。

    未授权,侵权删。

    原博文链接:http://blog.163.com/gc_chdch@126/blog/static/172279052201641935828402/

    ---------------------------------------------------------------------------------

    学习总结:初等数论(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中重复的要去掉。

     

    代码:

      1 #include  <cstdio>
      2 
      3 #include  <cstring>
      4 
      5 #include  <algorithm>
      6 
      7 #include  <cmath>
      8 
      9 #include  <vector>
     10 
     11 #include  <map>
     12 
     13 using  namespace std;
     14 
     15  
     16 
     17 typedef  long long LL;
     18 
     19  
     20 
     21 int  gcd(int a, int b)
     22 
     23 {
     24 
     25   return b == 0 ? a : gcd(b, a % b);
     26 
     27 }
     28 
     29  
     30 
     31 void  _gcd(int a, int b, LL &x, LL &y)
     32 
     33 {
     34 
     35   if (b == 0)
     36 
     37   {
     38 
     39       x = 1; y = 0;
     40 
     41       return ;
     42 
     43   }
     44 
     45   _gcd(b, a%b, y, x);
     46 
     47   y -= (a/b) * x;
     48 
     49 }
     50 
     51  
     52 
     53 int  extend_gcd(int a, int b, int c, LL &x, LL &y, int &dx, int  &dy)
     54 
     55 {
     56 
     57   int g = gcd(a, b);
     58 
     59   if (c % g) return 0;
     60 
     61   _gcd(a, b, x, y);
     62 
     63   x *= (c/g);
     64 
     65   y *= (c/g);
     66 
     67   dx = b / g;
     68 
     69   dy = a / g;
     70 
     71   return g;
     72 
     73 }
     74 
     75  
     76 
     77 // Ax  = B (mod N)
     78 
     79 // 设Ax =  -yN + B
     80 
     81 // 则Ax +  Ny = B
     82 
     83 bool  line_mod_equ(int A, int B, int N, int &x, int &k)
     84 
     85 {
     86 
     87   LL x0, y0;
     88 
     89   int dx, dy;
     90 
     91   if (!extend_gcd(A, N, B, x0, y0, dx, dy))  return false;
     92 
     93   x0 %= dx;
     94 
     95   if (x0 < 0) x0 += dx;
     96 
     97   x = (int)x0;
     98 
     99   k = dx;
    100 
    101   return true;
    102 
    103 }
    104 
    105  
    106 
    107 LL  pow_mod(LL a, LL b, LL p)
    108 
    109 {
    110 
    111   if (b == 0) return 1;
    112 
    113   LL tmp = pow_mod(a, (b>>1), p);
    114 
    115   if (b & 1) return tmp * tmp % p * a %  p;
    116 
    117       else return tmp * tmp % p;
    118 
    119 }
    120 
    121  
    122 
    123 //分解质因数
    124 
    125 void  factor(int x, vector<int> &divs)
    126 
    127 {
    128 
    129   divs.clear();
    130 
    131   for (int i = 2; i * i <= x; ++ i)
    132 
    133       if (x % i == 0)
    134 
    135       {
    136 
    137          divs.push_back(i);
    138 
    139          while (x % i == 0) x /= i;
    140 
    141       }
    142 
    143   if (x > 1) divs.push_back(x);
    144 
    145 }
    146 
    147  
    148 
    149 bool  g_test(int g, vector<int> &divs, int P)
    150 
    151 {
    152 
    153   for (int i = 0; i < (int) divs.size();  ++ i)
    154 
    155       if (pow_mod(g, (P-1) / divs[i], P) ==  1) return false;
    156 
    157   return true;
    158 
    159 }
    160 
    161  
    162 
    163 //找原根,p为质数,保证有原根
    164 
    165 int  primitive_root(int P)
    166 
    167 {
    168 
    169   static vector<int> divs;
    170 
    171   factor(P-1, divs);
    172 
    173   int g = 1;
    174 
    175   while (!g_test(g, divs, P)) ++ g;
    176 
    177   return g;
    178 
    179 }
    180 
    181  
    182 
    183 // 求解在模P意义下,以a为底N的离散对数b (P为质数)
    184 
    185 // 即 a ^ b  = N (mod P)
    186 
    187 // 大步小步算法(分块)
    188 
    189 // 取s =  sqrt(P), 设b = x * s + y
    190 
    191 // 则 a ^  (x*s + y) = (a^s)^x * a^y = N (mod P)
    192 
    193 // 求出y =  0~s-1时, a^y的取值;然后枚举s,算出(a^s)^x,查找是否有匹配的y
    194 
    195 int  discrete_log(int a, int N, int P)
    196 
    197 {
    198 
    199   map<int, int> rec;
    200 
    201   int s = (int)sqrt(P + 0.5);
    202 
    203   while (s * s <= P) ++ s;
    204 
    205   LL cur = 1;
    206 
    207   for (int y = 0; y < s; ++ y)
    208 
    209   {
    210 
    211       rec[ cur ] = y;
    212 
    213       cur = cur * a % P;
    214 
    215   }
    216 
    217   LL a_s = cur; // a^s
    218 
    219   cur = 1;
    220 
    221   for (int x = 0; x < s; ++ x)
    222 
    223   {
    224 
    225       LL a_y = pow_mod(cur, P-2, P) * LL(N) %  P;
    226 
    227       map<int,int> :: iterator it =  rec.find( a_y );
    228 
    229       if (it != rec.end()) return x * s + it  -> second;
    230 
    231       cur = cur * a_s % P;
    232 
    233   }
    234 
    235   return -1;
    236 
    237 }
    238 
    239  
    240 
    241 // x ^  K = A (mod P) (where P is a prime)
    242 
    243 // 找p的一个原根g,求出指标
    244 
    245 // K  I(x) = I(A) (mod P-1)
    246 
    247 // 有解的条件 gcd(  I(x), P-1 ) | I(A)
    248 
    249 void  discrete_root(int K, int A, int P, vector<int> &x)
    250 
    251 {
    252 
    253   x.clear();
    254 
    255   if (A == 0) { x.push_back(0); return ; }
    256 
    257   int g = primitive_root(P);
    258 
    259   int IA = discrete_log(g, A, P);
    260 
    261   int Ix, delta;
    262 
    263   if (!line_mod_equ(K, IA, P-1, Ix, delta))  return ;
    264 
    265   while (Ix < P)
    266 
    267   {
    268 
    269       x.push_back( pow_mod(g, Ix, P) );
    270 
    271       Ix += delta;
    272 
    273   }
    274 
    275   sort(x.begin(), x.end());
    276 
    277   x.erase(unique(x.begin(), x.end()),  x.end());
    278 
    279 }
    280 
    281  
    282 
    283 int  main()
    284 
    285 {
    286 
    287   int P, K, A;
    288 
    289   scanf("%d%d%d", &P, &K,  &A);
    290 
    291   static vector<int> x;
    292 
    293   discrete_root(K, A, P, x);
    294 
    295   printf("%u
    ", x.size());
    296 
    297   for (int i = 0; i < (int) x.size(); ++  i) printf("%d
    ", x[i]);
    298 
    299   return 0;
    300 
    301 }
  • 相关阅读:
    JqueryDataTable的使用(.Net平台)
    MvcPager使用的Demo(同步分页)
    第一篇随笔
    基于querybuilder的可根据现有数据表自动生成Restful API的dotnet中间件
    EF Core懒人小技巧之拒绝DbSet
    【基于EF Core的Code First模式的DotNetCore快速开发框架】完成对DB First代码生成的支持
    基于EF Core的Code First模式的DotNetCore快速开发框架
    Service Fabric独立集群搭建
    Win10打补丁KB4022725出现0x80073712错误
    【懒人有道】在asp.net core中实现程序集注入
  • 原文地址:https://www.cnblogs.com/KonjakJuruo/p/5847140.html
Copyright © 2011-2022 走看看