数论板子大总结
1.欧几里得求gcd
int gcd(int a,int b) { if(b==0) return a; return gcd(b,a%b); }
2.扩展欧几里得求解线性方程
首先根据Bezout定理,对于任意的a,b:ax+by=gcd(a,b);
然后根据欧几里得求gcd的方法构成等价方程,在递归的同时求出x,y;
void exgcd(int a,int b,int &x,int &y) { if(b==0){ x=1;y=0; return; } gcd(b,a%b,y,x); y-=a/b*x; }
另外一种写法:(不只是long long 的事情)
void exgcd(long long a,long long b,long long& x,long long& y) { if(!b){ x=1;y=0; return; } exgcd(b,a%b,x,y); long long tmp=x; x=y; y=tmp-a/b*y; }
3.扩展欧几里得求逆元
这其实与上面的求线性方程的本质是一样的,这里就不多说了
void exgcd(long long a,long long b,long long& x,long long& y) { if(!b){ x=1;y=0; return; } exgcd(b,a%b,x,y); long long tmp=x; x=y; y=tmp-a/b*y; }
4.费马小定理+快速幂求逆元
long long KSM(long long a,long long b) { long long res=1; while(b){ if(b&1) res=res*a%p; a=a*a%p; b/=2; } return res; } //inv=KSM(除数,模数-2);
5.线性递推求解逆元
k∗i+r≡0(modp)
k∗(r的逆元)+(l的逆元)≡0(modp)
(l的逆元)≡−k∗(r的逆元)(modp)
(l的逆元)≡−⌊p/i⌋∗((p%i)的逆元)(modp)
#include <bits/stdc++.h> #define p 1000000007 using namespace std; long long inv[100010]; int main() { int n; cin>>n; inv[1]=1; for(int i=2;i<=n;i++){ inv[i]=(p-p/i)*inv[p%i]%p; } for(int i=1;i<=n;i++){ cout<<inv[i]<<" "; } }
6.高斯消元求解线性方程组
对于一个未知量xi,找到一个xi的系数非0,但x1~xi-1的系数都是零的方程,然后用初等行变换把其他方程的xi的系数全部消成0;
在高斯消元完成后,若存在系数都是0,但常数不是0的情况,方程无解;
通常情况下:一道题并不会直接给出n个n元一次方程组。如果给了n+1个二元方程组,那么我们可以通过相邻两个方程做差,把它变成n个n元一次方程组,然后进行高斯消元求解
#include <bits/stdc++.h> #define eps 1e-8 using namespace std; double a[101][102]; int n; bool GUASS() { for(int i=1;i<=n;i++){ for(int j=i;j<=n;j++){ if(fabs(a[j][i])>eps){ for(int k=1;k<=n;k++){ swap(a[i][k],a[j][k]); } swap(a[i][n+1],a[j][n+1]); break; } } for(int j=1;j<=n;j++){ if(i==j) continue; double tmp=a[j][i]/a[i][i]; for(int k=i;k<=n;k++){ a[j][k]-=a[i][k]*tmp; } a[j][n+1]-=a[i][n+1]*tmp; } } for(int i=1;i<=n;i++){ bool lala=0; for(int j=1;j<=n;j++){ if(a[i][j]!=0){ lala=1; } } if(lala==0){ cout<<"No Solution"; return 0; } } return 1; } int main() { cin>>n; for(int i=1;i<=n;i++){ for(int j=1;j<=n+1;j++){ scanf("%lf",&a[i][j]); } } if(GUASS()){ for(int i=1;i<=n;i++){ printf("%.2lf\n",a[i][n+1]/a[i][i]); } } }
7.利用高斯消元求取矩阵的逆元:
作用:求X=A/B; (X*B=A,X*B*B的逆矩阵=A*B的逆矩阵,X=A*B的逆矩阵);(任何矩阵*单位矩阵=单位矩阵)
方法:在读入的n阶矩阵右侧加入一个n阶单位矩阵,然后做高斯消元,使读入的n阶矩阵化为n阶单位矩阵,此时右面加入的矩阵就为所求的逆。
特点:一个可逆矩阵,它的逆矩阵是唯一的;
#include <bits/stdc++.h> #define p 1000000007 #define inc(i,a,b) for(register int i=a;i<=b;i++) using namespace std; int n; int a[1000][1000]; long long KSM(long long a,long long b) { long long res=1; while(b){ if(b&1) res=res*a%p; a=a*a%p; b/=2; } return res%p; } int Gauss() { inc(i,1,n){ inc(j,i,n) if(a[j][i]) {swap(a[j],a[i]);break;} if(a[i][i]==0){cout<<"No Solution"; return 0;} long long tmp=KSM(a[i][i],p-2); inc(j,i,2*n) a[i][j]=a[i][j]*tmp%p; inc(j,1,n){ if(i==j) continue; long long tmp2=a[j][i]%p; inc(k,i,2*n) a[j][k]=(a[j][k]-tmp2*a[i][k]%p+p)%p; } } return 1; } int main() { cin>>n; inc(i,1,n){ inc(j,1,n) scanf("%d",&a[i][j]); a[i][i+n]=1; } if(Gauss()){ inc(i,1,n){ inc(j,n+1,2*n){ printf("%d ",a[i][j]); } printf("\n"); } } }
8.线性筛质数
简单的模板
void prime(int n) { int m=0; for(int i=2;i<=n;i++){ if(vis[i]==0){ vis[i]=i; prime[++m]=i; } for(int j=1;j<=m;j++){ if(prime[j]>vis[i]||prime[j]>n/i) break; vis[i*prime[j]]=prime[j]; } } }
9.线性筛求解欧拉函数
特殊的:phi[1]=1;
phi[n]=phi[n/p]*p(p是质数且p方整除n)
phi[n]=phi[n/p]*(p-1)(p是质数且p方不整除n)
void prime(int n) { int m=0; for(int i=2;i<=n;i++){ if(vis[i]==0){ vis[i]=i; prime[++m]=i; phi[i]=i-1; } for(int j=1;j<=m;j++){ if(prime[j]>vis[i]||prime[j]>n/i) break; vis[i*prime[j]]=prime[j]; phi[i*prime[j]]=phi[i]*(i%prime[j]?prime[j]-1:prime[j]); } } }
10.O(sqrt(n))求解一个数的欧拉函数
从欧拉函数的定义而来,没什么特别的
void prime(int n) { int m=0; for(int i=2;i<=n;i++){ if(vis[i]==0){ vis[i]=i; prime[++m]=i; phi[i]=i-1; } for(int j=1;j<=m;j++){ if(prime[j]>vis[i]||prime[j]>n/i) break; vis[i*prime[j]]=prime[j]; phi[i*prime[j]]=phi[i]*(i%prime[j]?prime[j]-1:prime[j]); } } }
11.欧拉定理的推论求解快速幂
#include <iostream> #include <cstdio> #include <cmath> #define ll long long using namespace std; ll a,m,b; inline ll read(ll m){ register ll x=0,f=0;char ch=getchar(); while(!isdigit(ch)) ch=getchar(); while(isdigit(ch)){ x=x*10+ch-'0'; if(x>=m) f=1; x%=m;ch=getchar(); } return x+(f==1?m:0); } ll phi(ll n){ ll ans=n,m=sqrt(n); for(ll i=2;i<=m;i++){ if(n%i==0){ ans=ans/i*(i-1); while(n%i==0) n/=i; } } if(n>1) ans=ans/n*(n-1); return ans; } ll fast_pow(ll a,ll b,ll p){ ll ret=1; for(;b;b>>=1,a=a*a%p) if(b&1) ret=ret*a%p; return ret; } int main() { cin>>a>>m; b=read(phi(m)); cout<<fast_pow(a,b,m); return 0; }
12.BSGS求解高次同余方程
利用了跳过无用枚举的技巧巧妙地将O(nlogn)的算法优化成了O(sqrt(n)logn),并保证了正确性;
但是map的映射或许会加大程序的常数;
void BSGS(long long a, long long ans, long long p) { map<long long, long long> Myhash; ans %= p; int tmp = sqrt(p) + 1; for (int i = 0; i < tmp; i++) { Myhash[(ans * KSM(a, i, p)) % p] = i; } a = KSM(a, tmp, p) % p; if (a == 0 && ans == 0) { cout << "1" << endl; return; } if (a == 0 && ans != 0) { cout << "Orz, I cannot find x!" << endl; return; } for (int i = 0; i <= tmp; i++) { if (Myhash.find(KSM(a, i, p)) != Myhash.end() && (i * tmp - Myhash[KSM(a, i, p)] >= 0)) { cout << i * tmp - Myhash[KSM(a, i, p)] << endl; return; } } cout << "Orz, I cannot find x!" << endl; }