结论:我数论太渣了……

言归正传……先列出几个常用的性质/结论

同余式:

1. da≡db (mod m) 则a≡b (mod m/(m,d) ) (这在取遍剩余系会用到)

2. a≡b (mod m)  m'|m , a≡b (mod m')

3. a≡b (mod mi) i=1..k 等价于 a≡b (mod M) M=[m1,m2,..mk]

一般剩余系:

剩余系就是可能余数组成的集合,简化剩余系也称既约剩余系,是模n的完全剩余系的一个子集,其中每个元素与n互素。

1. 模p的剩余系{rk},若(p,d)=1那么{d*rk}也是模p的剩余系

这个很重要,如果求ax mod p的既约剩余系的大小,我们只要利用同余式的性质1

转化为x*a/gcd(a,p) mod (p/gcd(a,p)),根据这个理论 x取值个数就是p/gcd(a,p)

2. 设模m1的既约剩余系为R1,m2的既约剩余系为R2,m1m2互质,则模m1m2的既约剩余系为R={x=m2x1+m1x2 (mod m1m2) | x1∈R1,x2∈R2}

这个结论证明可以考虑任意两个x互不同余即可(作差判断)

这就告诉我们一个很厉害的东西 S(M)表示M既约剩余系的大小的话,S(M)=S(m1)*S(m2)

3. 这个结论推广到k维就是,设模mk的既约剩余系为Rk, mk两两互质,M=∏mi 则模M的既约剩余系为R={ ∑M*mi-1*ai (mod M) | xi∈Ri }  

因此以后我们求模M的剩余系大小,可以对M质因数分解分别求出后再相乘

 

阶、原根、指标:

 阶(又叫指数):a,m为互素整数且m>=2 ,则使 ar≡1(mod m) 成立的最小正整数 r 记为 a 模 m 的指数,记作ord_m(a)

根据欧拉定理,显然ord_m(a)一定是φ(m)的约数

阶有几个性质: 1. ord_m(a)=xy 则 ord_m(ax)=y 2.ord_m(a)=x, ord_m(b)=y (x,y)=1 则ord_m(ab)=xy;

3. ord_m(a)=t ord_m(ax)=t/gcd(t,x) (其实就是性质1的变形)

原根:阶中要求(a,m)互素,所以a^x与m互素,所以ax模m的余数与m互素,这样这个余数只可能有 φ(m) 种不同的取值。

(这里我想到了了一个很有意思的结论:1~n内与n互质的数的和为{n*φ(n)+[n==1]} /2 

这里考虑gcd(i,n)=1,则gcd(n,n-i)=1,证明用反证法即可。)

当满足ord_m(a)=φ(m)的a称作模m的原根,记作g。

这有一个显然的结论若 x=0,1,2,3,...,φ(m)-1,则 gx 模 m 的余数都和 m 互素,并且模 m 两两不同余。

指标:简单来说,就是一切小于m且与m互质的数r,都存在唯一个数k(k<φ(m)),使得gk≡r (mod m)

我们就把k叫作r的指标,记作I(r)

这是一个非常有用的性质,之后的题目我们会经常看到他。

指标有这么几个性质:1. I(ab)≡I(a)+I(b) (mod m) 2. I(ak)≡kI(a) (mod m)

简而言之,指标就像是数论里的取对数。

原根和求指标将在后面的同余方程中起到巨大作用,但是首先我们要知道什么数有原根

直接上结论: 当且仅当m=2,4,pk,2pk时才有原根,p是某一奇素数

证明可以看《初等数论及其应用》

还有一个非常有用的定理是:若g是某一奇素数p的原根,那么g也是pk的原根,证明依然在《初等数论及其应用》

事实上,一个数p的原根的数目为φ(φ(p)) (考虑若g是p的原根,gu是p的原根当且仅当gcd(u,φ(p))=1)

另外我们注意到2的原根是1,4的原根是3,但其他2次幂是没有原根的(在高次剩余里,模2次幂会带来不小的麻烦)

事实上有一个恒等式:aφ(xy)/2≡1 (mod xy) (x,y互素且a与xy互素)

在2次幂的时候就有a^(2k)≡1 (mod 2k+2) 恒成立,所以2的3次及以上次幂没有原根

(补充一个很有意思的结论:5模2k+2的指数正好是2k

一个数的原根不止一个,一般就找最小的那个就可以了

其他的原根可用最小原根的gu表示(gu是p的原根当且仅当gcd(u,φ(p))=1)

找m的最小原根一般就是暴力枚举g,然后穷举m的每个质因数p,判断gφ(m)/p≡1 (mod m)是否成立(成立的话就不是原根)

一道找所有原根的题目

  1 #include<bits/stdc++.h>
  2 
  3 using namespace std;
  4 typedef long long ll;
  5 int phi[1000010],p[1000010],a[100010],ans[1000010],n,r,t;
  6 bool v[1000010];
  7 
  8 int gcd(int a,int b)
  9 {
 10     return (b==0)?a:gcd(b,a%b);
 11 }
 12 
 13 ll quick(ll x,int y,int mo)
 14 {
 15     ll s=1;
 16     while (y)
 17     {
 18         if (y&1) s=s*x%mo;
 19         x=x*x%mo;
 20         y>>=1;
 21     }
 22     return s;
 23 }
 24 
 25 int check(int n)
 26 {
 27     if (n%2==0) return -1;
 28     if (!v[n]) return n;
 29     for (int i=2; p[i]*p[i]<=n; i++)
 30         if (n%p[i]==0)
 31         {
 32             while (n%p[i]==0) n/=p[i];
 33             if (n>1) return -1;
 34             else return p[i];
 35         }
 36     return -1;
 37 }
 38 
 39 bool work(int g,int mo)
 40 {
 41     if (gcd(g,mo)!=1) return 0;
 42     for (int i=1; i<=r; i++)
 43         if (quick(g,phi[mo]/a[i],mo)==1) return 0;
 44     return 1;
 45 }
 46 
 47 int getg(int mo)
 48 {
 49     int n=phi[mo];
 50     r=0;
 51     for (int i=1; p[i]*p[i]<=n; i++)
 52         if (n%p[i]==0)
 53         {
 54             while (n%p[i]==0) n/=p[i];
 55             a[++r]=p[i];
 56         }
 57     if (n>1) a[++r]=n;
 58     for (int i=2; i<mo; i++)
 59         if (work(i,mo)) return i;
 60 }
 61 
 62 int main()
 63 {
 64     phi[1]=1;
 65     for (int i=2; i<=1000000; i++)
 66     {
 67         if (!v[i])
 68         {
 69             phi[i]=i-1;
 70             p[++t]=i;
 71         }
 72         for (int j=1; j<=t; j++)
 73         {
 74             if (i*p[j]>1000000) break;
 75             v[i*p[j]]=1;
 76             if (i%p[j]==0)
 77             {
 78                 phi[i*p[j]]=phi[i]*p[j];
 79                 break;
 80             }
 81             else phi[i*p[j]]=phi[i]*(p[j]-1);
 82         }
 83     }
 84     while (scanf("%d",&n)!=EOF)
 85     {
 86         if (n==2)
 87         {
 88             puts("1");
 89             continue;
 90         }
 91         if (n==4)
 92         {
 93             puts("3");
 94             continue;
 95         }
 96         int ch=(n%2==1)?check(n):check(n/2);
 97         if (ch==-1)
 98         {
 99             puts("-1");
100             continue;
101         }
102         int g=getg(n);
103         int l=0;
104         for (int i=1; i<=phi[n]; i++)
105             if (gcd(i,phi[n])==1) ans[++l]=quick(g,i,n);
106         sort(ans+1,ans+1+l);
107         for (int i=1; i<=l; i++)
108         {
109             printf("%d",ans[i]);
110             if (i==l) puts(""); else printf(" ");
111         }
112     }
113 }
hdu4992

高次同余相关的问题:

高次同余问题一般都会把余数b进行ga≡b (mod p)这样的变化

这样统一底后进行两边取指标,这样就将问题转化为了线性同余问题

比如今年第二场多校的hdu6051,就利用了将高次同余都转化为原根为底的形式

具体题解在:http://bestcoder.hdu.edu.cn/blog/2017-multi-university-training-contest-2-solutions-by-%E7%94%B5%E5%AD%90%E7%A7%91%E6%8A%80%E5%A4%A7%E5%AD%A6/

 1 #include<bits/stdc++.h>
 2 
 3 using namespace std;
 4 typedef long long ll;
 5 const int mo=1e9+7;
 6 int m,p;
 7 ll sqr(ll x)
 8 {
 9     return x*x%mo;
10 }
11 
12 void inc(int &a,int b)
13 {
14     a+=b;
15     if (a>mo) a-=mo;
16 }
17 
18 int phi(int n)
19 {
20     int s=n;
21     for (int i=2; i*i<=n; i++)
22         if (n%i==0)
23         {
24             s=s/i*(i-1);
25             n/=i;
26             while (n%i==0) n/=i;
27         }
28     if (n>1) s=s/n*(n-1);
29     return s;
30 }
31 
32 int calc(int n)
33 {
34     if (n==1) return 1;
35     return 1ll*n*phi(n)/2%mo;
36 }
37 
38 int main()
39 {
40     int cas;
41     scanf("%d",&cas);
42     for (int tt=1; tt<=cas; tt++)
43     {
44         scanf("%d%d",&m,&p);
45         int ans=0;
46         for (int i=1; i*i<=(p-1); i++)
47             if ((p-1)%i==0)
48             {
49                 inc(ans,sqr(i)*calc((p-1)/i)%mo);
50                 int j=(p-1)/i;
51                 if (j!=i) inc(ans,sqr(j)*calc((p-1)/j)%mo);
52             }
53         int s=1ll*(p-1)*p/2%mo;
54         ans=(ans-s+mo)%mo;
55         ans=1ll*ans*m%mo;
56         printf("Case #%d: %d\n",tt,ans);
57     }
58 }
hdu6051

 

还有著名的数论之神,题解我是看这的:http://blog.csdn.net/regina8023/article/details/44863519

(可以注意到模数是奇数,也就是不会考虑到2次幂的情况,降低了难度)

  1 #include<bits/stdc++.h>
  2 
  3 using namespace std;
  4 typedef long long ll;
  5 const int inf=1e9+7;
  6 int a[100010],A,B,n,t;
  7 map<int,int> mp;
  8 ll quick(ll x,int y,int mo)
  9 {
 10     ll s=1;
 11     while (y)
 12     {
 13         if (y&1) s=s*x%mo;
 14         x=x*x%mo;
 15         y>>=1;
 16     }
 17     return s;
 18 }
 19 
 20 int gcd(int a,int b)
 21 {
 22     return (b==0)?a:gcd(b,a%b);
 23 }
 24 
 25 int chai(int n,int p)
 26 {
 27     int s=0;
 28     while (n%p==0)
 29     {
 30         s++;
 31         n/=p;
 32     }
 33     return s;
 34 }
 35 
 36 void exgcd(int a, int b, int &x, int &y)
 37 {
 38      if (!b){x=1; y=0; return;}
 39      else {
 40           exgcd(b,a%b,x,y);
 41           int xx=x, yy=y;
 42           x=yy; y=xx-a/b*yy;
 43      }
 44 }
 45 
 46 int bsgs(int y,int z,int p)
 47 {
 48     y%=p; z%=p;
 49     mp.clear();
 50     mp[1]=p-1;
 51     ll m=(int)sqrt(p)+1, now=1;
 52     for (int i=1; i<=m; i++)
 53     {
 54         now=1ll*now*y%p;
 55         if (!mp[now]) mp[now]=i;
 56     }
 57     int g=gcd(now,p);
 58     if (g!=1) return -1;
 59     now/=g; int w=p/g,x,k;
 60     exgcd(now,w,x,k); x=(x+w)%w;
 61     ll step=z;
 62     for (int i=0; i<m; i++)
 63     {
 64         if (mp[step])
 65         {
 66            if (step==1) mp[step]=0;
 67            return mp[step]+i*m;
 68         }
 69         step=step*(ll)x%p;
 70     }
 71     return -1;
 72 }
 73 
 74 bool check(int g,int p)
 75 {
 76     for (int i=1; i<=t; i++)
 77         if (quick(g,(p-1)/a[i],p)==1) return 0;
 78     return 1;
 79 }
 80 
 81 int getg(int p)
 82 {
 83     t=0; int n=p-1;
 84     for (int i=2; i*i<=n; i++)
 85         if (n%i==0)
 86         {
 87             a[++t]=i;
 88             while (n%i==0) n/=i;
 89         }
 90     if (n>1) a[++t]=n;
 91     for (int i=2;;i++)
 92         if (check(i,p)) return i;
 93 }
 94 
 95 int calc(int p,int t,int nw,int B)
 96 {
 97     if (B==0)
 98     {
 99         int s=(t-1)/A+1;
100         return quick(p,t-s,inf);
101     }
102     else {
103         int s=chai(B,p),f=1;
104         if (s)
105         {
106             if (s%A!=0) return 0;
107             f=quick(p,s-s/A,inf);
108             int gg=gcd(nw,B);
109             nw/=gg; B/=gg;
110         }
111         int phi=nw/p*(p-1);
112         int nb=bsgs(getg(p),B,nw);
113         if (nb==-1) return 0;
114         int gg=gcd(phi,A);
115         if (nb%gg) return 0;
116         return f*gg;
117     }
118 }
119 
120 int main()
121 {
122     int cas;
123     scanf("%d",&cas);
124     while (cas--)
125     {
126         scanf("%d%d%d",&A,&B,&n);
127         n=2*n+1; B%=n;
128         int ans=1;
129         for (int i=2; i*i<=n; i++)
130             if (n%i==0)
131             {
132                 int s=0,nw=1;
133                 while (n%i==0)
134                 {
135                     s++; nw*=i;
136                     n/=i;
137                 }
138                 ans*=calc(i,s,nw,B%nw);
139                 if (!ans) break;
140             }
141         if (n>1) ans*=calc(n,1,n,B%n);
142         printf("%d\n",ans);
143     }
144 }
bzoj2219

 

前面的几题是高次同余方程的问题,那么高次剩余系的问题呢

下面我觉得是一道最具有代表性的题目:gym101177H 

这是South Pacific Regionals 2016的题目,就是求(xa+q) mod n的余数的种类数(显然q是卖萌的)

由之前剩余系的基本性质,只要求xa mod piki 的余数种类数,然后求积就可以了

这要分情况,假如pi是奇素数,那么piki是有原根的,假设为g,我们不妨先考虑与piki互质的x可能的余数

令gw≡x (mod piki) 由之前原根的性质,这样就是求wa mod φ(piki)可能的余数个数

显然这个个数就是 φ(piki)/gcd( φ(piki),a)

而与 piki不互质的x余数的种类数,我们只要枚举x包含pi的几次幂为因数,设x=q*piei

问题就是转化为xmod piki-a*ei且x与pi互质的情况了

难点在于考虑p=2的情况,因为除了2,4,p不存在原根,这怎么考虑呢

还是想刚才一样分x为奇偶考虑(偶数是类似之前处理)

注意到当a为奇数的时候,所有一共2k-1个奇数都是可能的余数

证明的话考虑任意两个奇数x1,x2,x1a-x2a=(x1-x2)(x1a-1+x1p-2x2+...+x2p-1)

前一项是偶数但是一定小于2k,后一项是奇数,因此x1a-x2a模2k不同余,余数自然可以取到2k-1个奇数

下面考虑a为偶数的情况,显然a可以表示a=q*2e   (q为奇数)

由之前a为奇数的情况我们知道x相当于把所有奇数做了一个置换,因此我们只要考虑x^(2e) mod 2k可能的余数

这有点二次剩余的味道,首先很明显x2和(2k-x)2一定是mod 2k同余的

进一步观察我们会发现x2和(2k-1-x)2也是mod 2k同余的,就是当e=1时,只能取2k-3

之后e每增加1,也就是多一个平方,就会使两两一组奇数变为同余,所以答案为2k-2-e

问题得解,撒花

 1 #include<bits/stdc++.h>
 2 
 3 using namespace std;
 4 int k,q,n;
 5 int quick(int x,int y)
 6 {
 7     int s=1;
 8     while (y)
 9     {
10         if (y&1) s=s*x;
11         x=x*x;
12         y>>=1;
13     }
14     return s;
15 }
16 
17 int gcd(int a,int b)
18 {
19     return (b==0)?a:gcd(b,a%b);
20 }
21 
22 int calc(int p,int x)
23 {
24     if (x<=0) return 1;
25     if (p>2)
26     {
27         int phi=quick(p,x-1)*(p-1);
28         int s=phi/gcd(phi,k);
29         s+=calc(p,x-k);
30         return s;
31     }
32     if (x==1) return 2;
33     int phi=quick(2,x-2);
34     int s=phi/gcd(phi,k);
35     if (k%2==1) s*=2;
36     s+=calc(p,x-k);
37     return s;
38 }
39 
40 int main()
41 {
42     scanf("%d%d%d",&k,&q,&n);
43     if (k==1)
44     {
45         printf("%d\n",n);
46         return 0;
47     }
48     int ans=1;
49     for (int i=2; 1ll*i*i<=n; i++)
50         if (n%i==0)
51         {
52             int s=0;
53             while (n%i==0)
54             {
55                 n/=i;
56                 ++s;
57             }
58             ans*=calc(i,s);
59         }
60     if (n>1) ans*=calc(n,1);
61     printf("%d\n",ans);
62 }
gym101177H

 

上面那个问题让我想到一个非常有意思的东西叫二次剩余

就是说x2≡b (mod p)有解,就把b叫作p的二次剩余

如果我们定义(a/p)=1 (a是p的二次剩余) 或 -1(a不是p的二次剩余)

那么有一个非常有意思的定理(欧拉判别法)是(a/p)≡a(p-1)/2 (mod p) p是奇素数 a不被p整除

由此可以退出还有二次剩余乘二次剩余一定是二次剩余,二次剩余乘非二次剩余是非二次剩余,非二次剩余乘非二次剩余是二次剩余

还有一个著名的二次互反律:设p,q为奇素数

(q/p)(p/q)=(-1)(p-1)(q-1)/4 就是由q是p的二次剩余如何快速判断p是q的二次剩余

如果把二次剩余的结论推广到n次,则是这样的(但似乎没有类似高次互反律的东西)

前提:p是奇质数且p不能整除d,判定xn≡d (mod p)是否有解

1.若n|(p-1)(即n能整除p-1),则d是模p的n次剩余的充要条件为:d(p-1)/n≡1(mod p)且有解时,解数为n。
2.若n不能整除p-1,则d是模p的n次剩余的充要条件为:d(p-1)/k≡1(mod p),其中k=(n,p-1),且有解时解数为k。

感觉关于二次剩余和二次互反律的题目还不多,就先写到这吧

 

最后补一个扩展BSGS的模板吧

  1 #include<iostream>
  2 #include<cstdio>
  3 #include<cstring>
  4 #include<algorithm>
  5 #include<cmath>
  6 #include<map>
  7 
  8 using namespace std;
  9 typedef long long ll;
 10 int A,p,B;
 11 map<int,int> mp;
 12 
 13 int gcd(int a,int b)
 14 {
 15     return (b==0)?a:gcd(b,a%b);
 16 }
 17 
 18 ll quick(ll x,int y,int mo)
 19 {
 20     ll s=1;
 21     while (y)
 22     {
 23         if (y&1) s=s*x%mo;
 24         x=x*x%mo;
 25         y>>=1;
 26     }
 27     return s;
 28 }
 29 
 30 void exgcd(int a, int b, int &x, int &y)
 31 {
 32      if (!b){x=1; y=0; return;}
 33      else {
 34           exgcd(b,a%b,x,y);
 35           int xx=x, yy=y;
 36           x=yy; y=xx-a/b*yy;
 37      }
 38 }
 39 
 40 int inv(int a,int p)
 41 {
 42     int g=gcd(a,p);
 43     if (g!=1) return -1;
 44     a/=g; int w=p/g,x,k;
 45     exgcd(a,w,x,k); x=(x+w)%w;
 46     return x;
 47 }
 48 
 49 int bsgs(int y,int z,int p)
 50 {
 51     y%=p;
 52     mp.clear();
 53     mp[1]=p-1;
 54     ll m=(int)sqrt(p)+1, now=1;
 55     for (int i=1; i<=m; i++)
 56     {
 57         now=now*(ll)y%p;
 58         if (!mp[now]) mp[now]=i;
 59     }
 60     int x=inv(now,p);
 61     if (x==-1) return -1;
 62     ll step=z;
 63     for (int i=0; i<m; i++)
 64     {
 65         if (mp[step])
 66         {
 67            if (step==1) mp[step]=0;
 68            return mp[step]+i*m;
 69         }
 70         step=1ll*step*x%p;
 71     }
 72     return -1;
 73 }
 74 
 75 int ext(int a,int b,int p)
 76 {
 77     if (b==1) return 0;
 78     ll t=gcd(a,p),d=1,k=0;
 79     while (t!=1)
 80     {
 81         if (b%t) return -1;
 82         ++k,b/=t,p/=t, d=d*(a/t)%p;
 83         if (b==d) return k;
 84         t=gcd(a,p);
 85     }
 86     b=1ll*b*inv(d,p)%p;
 87     int s=bsgs(a,b,p);
 88     if (s==-1) return -1;
 89     else return s+k;
 90 }
 91 
 92 int main()
 93 {
 94     while (scanf("%d%d%d",&A,&p,&B)!=EOF)
 95     {
 96         if (!A&&!p&&!B) break;
 97         int ans=ext(A,B,p);
 98         if (ans==-1) puts("No Solution");
 99         else printf("%d\n",ans);
100     }
101 }
View Code

 

posted on 2017-07-29 23:58  acphile  阅读(1222)  评论(0编辑  收藏  举报