结论:我数论太渣了……
言归正传……先列出几个常用的性质/结论
同余式:
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 }
高次同余相关的问题:
高次同余问题一般都会把余数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 }
还有著名的数论之神,题解我是看这的: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 }
前面的几题是高次同余方程的问题,那么高次剩余系的问题呢
下面我觉得是一道最具有代表性的题目: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
问题就是转化为xa mod 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为奇数的情况我们知道xq 相当于把所有奇数做了一个置换,因此我们只要考虑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 }
上面那个问题让我想到一个非常有意思的东西叫二次剩余
就是说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 }