数论总结(中) (中阶数论)
2018-01-10 14:26
169 查看
一.逆元的求解
1.引理:
a.费马小定理
假如\(a\)是一个整数,\(p\)是一个素数,\(gcd(a,p)=1\),那么有:\[a^{p-1}≡1(mod(p))\]
费马小定理应用:
[1]. 费马小定理降次:\(p\)是素数,\(a\),\(p\)互质,则\(a^b mod (p)={a^{(b) mod (p)}} mod (p)\)
[2]. 求质数逆元:若p是素数,\(\frac{1}{a}mod(p) = a^{p-2} mod(p)\)
注意:不代表\(ax≡1(mod (p))\)中\(x\)的最小正整数值是\(p-1\)。
b.欧拉定理φ
若\(n,a\)为正整数,且\(n,a\)互质,即\(gcd(n,a)=1\),则\[a^{φ(n)}≡1(mod (n))\]
费马小定理是欧拉定理的特殊情况,因为当\(n\)为素数时,\(φ(n)=n-1\)
扩展欧拉定理降次:
(1)当\(n>1\),\((a,n)=1\)时, \(a^b \% n=a^{b \% φ(n)} \% n\)
(2)当\(b\leq φ(n)\)时,直接计算即可。
(3)当\(b > φ(n)\)时 , \(a^b \% n = a^{b\% φ(n) + φ(n)} \% n\)
我是不会告诉你这个非常重要的。
欧拉定理应用2:
求解:\(a^x≡1 (mod (Q))\) , \(x>0\)那么\(x\)的最小解一定是\(φ(Q)\)的约数
由同余的同幂性可以得到\(x\)的解集为 \(φ(Q)\)的 约数与倍数。
欧拉定理的套路技巧
\(ORZ\) \(\ laofu\)队爷 , 这都能自己 \(yy\) 出来。我们假设要求多次方,形如\(a^{b^{c^d}} \%p\)这种鬼东西。 那么化式子:
\[a^{b^c }\%p = a^{b^c \% (\ \varphi(p)+[?]\ )}\%\varphi(p)\]
然后\(c\)是对\(\varphi(\varphi(p))\)取模,以此类推递归下去。
那么那个\([?]\)我们需要每次都判断一下真实\(b\)值与\(\varphi(p)\)的大小关系。
这样非常不好做,这里队爷们发明了一种非常神奇的做法。
假设我们递归到了求\(b^c \% (\ \varphi(p)+[?]\ )\) , 那么代码如下:
ll Mul(ll x1,ll x2,ll mod)[ return (x1*x2 < mod) ? (x1*x2) : (x1*x2%mod+mod); } ll Pow(ll bs,ll js,ll mod){ ll S = 1, T = bs; while(js){ if(js&1)S = Mul(S,T,mod); T = Mul(T,T,mod); js >>= 1; }return S; } ll Calc(int b,int phi(p)){ll js = Calc(c,phi(phi(p))); return Pow( b,c,phi(p) );}
其中变量名是递归求解的,上面的\(b,c,phi(p)\)是表示当前算到\(b^c \% (\ \varphi(p)+[?]\ )\)。
这样做的好处就是不用考虑\(b\)与\(\varphi(p)\)的大小关系对求值的影响了。
其实非常简单,观察一下我们刚才重载的乘法函数 \(Mul\) ,
如果\(x_1*x_2\)都已经大于\(\varphi(p)\)了,那么后面运算肯定大于\(\varphi(p)\) ,
所以之后的运算中始终要加上一个\(\varphi(p)\)。
2.单个逆元的计算
给定\(a,n\)\((n>1)\),\(gcd(a,n)=1\)计算\(a\)对模\(n\)的乘法逆元\(x\).方法1:用前面讲的Exgcd解方程\(ax≡1(mod (n))\)
即\(ax+ny=1\),得\(x\)的特解\(x_0\),则\(a^{-1} mod (n)=(x_0)mod (n)\),解唯一!方法2:利用欧拉定理\(a^{φ(n)}\)\(≡\)\(1\)\((mod (n))\)
\(a^{-1} mod (n)=(a^{φ(n)-1}mod (n))mod (n)\)计算\(a^{φ(n)-1}mod (n)\)调用快速幂\(pow(a,φ(n)-1)\)来计算,
方法3:当n为质数时,变为费马小定理降次:
\(a^{-1} mod (n)=(a^{n-2}mod (n))mod (n)\)3.线性求解逆元
代码如下:inv[1]=1; for(int i=2;i<=p;i++) inv[i]=(M - M/i) * inv[M%i] %M;
证明:
二.中国剩余定理(CRT)。
1.中国剩余定理
2.扩展中国剩余定理
同余方程中的\(n_1,n_2,...,n_k\)不两两互质怎么办?用扩展中国剩余定理:
两个方程\(x≡b_1 (mod (a_1))\)与\(x≡b_2 (mod (a_2))\)
那么有\(x = a_1*x_1 + b_1 = a_2*x_2 + b_2\);
我们要最小化\(x\),所以最小化\(x_1\)。
然后回代得到\(x\)的特解\(x'\),当然\(x'\)也是最小正整数解。
所以\(x\)的通解为:\(x = x' + k*lcm(a_1,a_2)\)。
所以得到了一个新的方程: \(x ≡ x' (mod (lcm(a1,a2)))\) ;
所以每次把两个方程合并成\(x≡x' (mod (lcm(a1,a2)))\),
然后用该方程与接下来的一个联立,依次求解即可得出结果。
最后得到一个总方程:\(x ≡ C (mod (P))\)
解这个方程即可得到整个方程组的根。
三.MR素数判断
1.二次勘探定理
若\(p\)是质数 、 \(x^2 ≡ 1(mod(p))\),那么\(x\)的解为 \(x=1\) 或 \(x=n-1\)
2.MR素数判断
还记得费马小定理吗?不记得就在上面先把模数\(P\)变为\(m*2^k\),然后依次使用二次勘探定理即可。
具体太长了,给一篇博客:戳我!
核心代码(检测\(n\)的素性):
//本代码只测试15次,次数越高,准确率越高。 for (int i = 1; i <= 15; i++){ ll a = rand() % (n - 1) + 1; ll x = pow(a, m); //计算a^m %n ll y; for (int j = 1; j <= k; j++){ y = mul(x, x); //计算x*x %n ,使用龟速乘 if (y == 1 && x != 1 && x != n - 1)return false; x = y; } if (y != 1)return false; }
实在看不懂背背板子就行了。
四.矩阵乘法
矩阵快速幂要用,这里不讲了给一个板子:struct matrix{ int a[5][5]; matrix(){ memset(a,0,sizeof(a)); } int *operator [](int x){ return a[x]; } matrix operator*(matrix &b){ matrix c; for(int i = 0 ; i < n ; i ++) for(int j = 0 ; j < n ; j ++) for(int k=0 ; k < n ; k ++) c[i][j]=(c[i][j]+1ll*a[i][k]*b[k][j])%mod; return c; } }T,S;
四.高斯消元
1.高斯消元求解方程组(Gauss)。
百度一下,你就知道,这里只给代码实现。注:mp
[n+1]为方程矩阵,其中mp[i][n+1]为第i个方程的常数项。
当然最后答案也存在mp[i][n+1]中,代码中输出\(No\) \(Sol\)是指有无数多个解。
具体看\(Luogu\)的板子题:戳这里!
补:省选中的高斯消元模板题:戳这里!
int main(){ cin >> n; for(int i = 1; i <= n; i ++) for(int j = 1; j <= n + 1; j ++) cin >> mp[i][j]; for(int j = 1; j <= n; j ++){ int rgt = 0; for(int i = j; i <= n; i ++) if(mp[i][j]){rgt = i; break;} if(!rgt)continue; if(rgt ^ j)swap(mp[rgt],mp[j]); if(mp[j][j] == 0){cout<<"No Solution"; return 0;} for(int i = j + 1; i <= n; i ++){ double div = mp[i][j] / mp[j][j]; for(int k = 1; k <= n + 1; k ++)mp[i][k] -= div*mp[j][k]; } } for(int j = n; j >= 1; j --){ mp[j][n+1] = mp[j][n+1] / mp[j][j]; for(int i = j-1; i >= 1; i --)mp[i][n+1] -= mp[j][n+1] * mp[i][j]; } for(int i = 1; i <= n; i ++)printf("%.2lf\n" ,mp[i][n+1]); return 0; }
2.高斯消元求解异或方程(Gauss_Xor)
其实差不多,具体流程如下:(1)找到当前列系数不为0的一行,并将其交换到当前行。
(2)用当前行去消 当前列系数不为0的所有行 , 只是运算为异或罢了。
(3)消到最后只剩下一个元,解出来。
(4)向上一路回带即可(具体怎么回带自己手玩一把即可)。
注意一下:
假设消除到某一行
(1)若所有系数都为0,而常数项不为0,那么则无解。
(2)若所有系数都为0,且常数项为0,那么则有无穷多个解。
然后常用的搭配为bitset(一个STL),真的是卡常数神器。
具体的使用方法给一篇博客:http://blog.csdn.net/qll125596718/article/details/6901935
一道板子题:[SDOI2010]外星千足虫
然后这题有点难度:[HNOI2011]XOR和路径
具体代码:(以上面的例题为例)
#include<bits/stdc++.h> #define ll long long #define IL inline #define RG register using namespace std; IL int gi(){ RG int date = 0, m = 1; RG char ch = 0; while(ch!='-'&&(ch<'0'||ch>'9'))ch = getchar(); if(ch == '-'){m = -1; ch = getchar();} while(ch>='0'&&ch<='9'){date=date*10+ch-'0';ch=getchar();} return date*m; } int n,m,Round; bitset<2050>mp[2050]; char s[2050][2050]; IL bool Gauss_Xor(){ for(RG int j = 1; j <= n; j ++){ RG int rgtpos = 0; for(RG int i = j; i <= m; i ++) if(mp[i][j]){rgtpos = i; break;} //找到一个当前位不为0的方程.... if(!mp[rgtpos][j])return false; //无解 if(rgtpos != j)swap(mp[j] , mp[rgtpos]); Round = max(Round,rgtpos); for(RG int i = j+1; i <= m; i ++) if(mp[i][j])mp[i] ^= mp[j]; } for(RG int j = n; j >= 1; j --){ //mp[j][n+1] 此时即为解; for(RG int i = j-1; i >= 1; i --) mp[i][n+1] = mp[i][n+1] ^ (mp[i][j]*mp[j][n+1]); } return true; } int main(){ n = gi(); m = gi(); //有m(m>=n)个方程,每个方程左边有n个系数. for(RG int i = 1; i <= m; i ++){ scanf("%s",s[i]+1); for(RG int j = 1; j <= n; j ++) mp[i][j] = s[i][j] - '0'; mp[i][n+1] = gi(); } Round = 0; if(!Gauss_Xor())printf("Cannot Determine"); //无解 else{ printf("%d\n",Round) ; for(RG int i = 1; i <= n; i ++) puts(mp[i][n+1] ? "?y7M#" : "Earth"); }return 0; }
五.组合数学。
1.数学表示:
\(C_m^n\)表示在\(m\)个中选\(n\)个的方案数。 (把\(m\)个无区别物品放到\(n\)个有区别篮子的方案数)\(P_m^n\)表示在\(m\)个中选\(n\)个的排列数。 (把\(n\)个有区别物品放到\(m\)个有区别篮子的方案数)
\(S_m^n\)表示斯特林数\(S(n,m)\)。 (把\(n\)个有区别物品放到\(m\)个无区别篮子,篮子不空的方案数)
2.常用公式:
\[C_n^m = C_{n-1}^{m-1}+C_{n-1}^{m}\]\[\prod_{n-m+1}^n = C_n^m*{m!}\]
\[n^m = \sum_{j=1}^{n}S^m_j*\prod_{n-j+1}^n =\sum_{j=1}^nS_j^m*{j!}C_n^j\]
\[C_n^k = C_n^{n-k}\]
\[(a+b)^n = \sum_{k=0}^nC_n^ka^{n-k}b^k(二项式定理)\]
\[S_m^n=S_{m}^{n-1}*m+S_{m-1}^{n-1} \]
\[C_n^{k+1} = C_n^k*\frac{n-k}{k+1}\]
\[S_m^n = 0(if(m>n))\]
\[\sum_{k=1}^n C_n^k*C_k^t = C_n^t * 2^{n-t}\]
\[m!*S_m^n = \sum_{i=0}^m (-1)^iC_m^i*(m-i)^n\]
自己用组合意义推一下即可,都不是很难。
3.可重组合数学:
a.可重排列
有\(k\)个元素,其中第\(i\)个元素有\(n_i\)个,求全排列数。\[P' = \frac{({ \Sigma_{i=1}^k n_i})!}{{n_1!}{n_2!}....{n_k!}}\]
即先做全排列,然后给每个元素编号,具体见蓝书P104
b.可重组合
有\(n\)个元素,每个元素可以选无穷多个,一共选\(k\)个,求方案数。\[C' = C^{n-1}_{k-n+1} = C^{k}_{k-n+1}\]
假设第\(i\)个元素选\(x_i\)个,那么原问题变为\(x_1+x_2+.....+x_n=k\)
我们令\(y_i = x_i + 1\),那么\(y_1+y_2+y_3+....+y_n = k+n\)
此时\(y>0\),即每个元素都要选。 所以等于在\(k+n\)个元素(\(k+n-1\)个空位)间放\(n-1\)个隔板。
六.数论分块。
这其实是因题目而定的啦。这里主要是讲这种思路(套路)。我们假设求解\(\sum_{i=1}^n \lfloor\frac{n}{i}\rfloor\)
那么应该怎么在\(\sqrt{n}\)的时间里求出来?
观察到\(\lfloor\frac{n}{i}\rfloor\)的取值只有\(\sqrt{n}\)个。
所以可以类似分块算法那样搞一个数论分块。
定理:若有一个值\(i\),那么数论分块中其同值上界为:
\[ceil = \biggl\lfloor\ \frac{n}{\lfloor\frac{n}{i}\rfloor} \biggr\rfloor\]
即在\([i,ceil]\)这一段区间内,\(\lfloor\frac{n}{i}\rfloor\)的取值是一样的。所以就可以直接计算这一整块的贡献。
具体代码如下(以求\(\sum_{i=1}^n \lfloor\frac{n}{i}\rfloor\)为例):
int l = 1 , r , ans = 0; while(l<=n){ r = n/(n/l); ans += (r-l+1)*(n/i); l = r + 1; }
思考一下如果是二维怎么办,如求\(\sum_{i=1}^{min(n,m)} \lfloor\frac{n}{i}\rfloor\lfloor\frac{m}{i}\rfloor\)
其实也非常简单啦,对于\(i\),\(j_1 = n/\lfloor\frac{n}{i}\rfloor\) , \(j_2 = m/\lfloor\frac{m}{i}\rfloor\) , 那么\(j = min(j_1,j_2)\)。
所以每次都取\(min\),然后不断跳即可。
数论分块真的非常重要,在后面的高级数论(如莫比乌斯反演)中会经常用到。