NOIP前 基础数学模板

版权声明:转载请注明出处 https://blog.csdn.net/weixin_42557561/article/details/83796372
  • 最大公因数&最小公倍数( g c d l c m gcd,lcm
  • 乘法逆元(三种方法)
  • 快速乘&快速幂
  • 线性筛-素数&最大质因数&最小质因数&欧拉函数
  • 扩展欧几里得
  • 质因数分解
  • 组合数
  • 高斯消元
  • 矩阵快速幂
  • 康拓展开
最大公约数&最小公倍数
inline int gcd(int x,int y){
	int z=x%y;
	while(z){x=y;y=z;z=x%y;}
	return y;
}
inline int lcm(int x,int y){
	return x/gcd(x,y)*y;//(先除后乘,不然容易爆int)
}

当然在求gcd的时候你也可以不用手写,可以直接调用stl的库__gcd()


乘法逆元

存在a的逆元:当且仅当 g c d ( a , p ) = 1 gcd(a,p)=1 (p为模数)(可以用扩展欧几里得来证明)
逆元的作用:模意义下的除法,可以不炸精度

1.线性递推

  • 线性递推只在a<=p的时候有用!!!
    因为对于大于p的数,这个就毫无意义了(仔细想一下推导的过程,不清楚可以再问哦)
  • 对模数没有特殊要求,但要注意数据范围是否支持O(n)
  • 仔细看一下数据范围
    (这道题是保证了 n &lt; p n&lt;p ,所以可以使用线性递推)
inv[1]=1;
for(int i=2;i<=n;++i)
	inv[i]=(p-p/i)*1ll*inv[p%i]%p;//注意乘long long

2.扩展欧几里得
将式子转化为 a x + p y = 1 ax+py=1
x x 就是a在模p意义下的逆元(调整 x 到0~m-1的范围中即可)
这种算法效率较高,常数较小,时间复杂度为O(ln n)
基本上适用于求所有拟元

inline void exgcd(int a,int b,int &x,int &y){
	if(!b){x=1;y=0;return;}
	exgcd(b,a%b,y,x);
	y-=a/b*x;
}
exgcd(a,p,x,y);
x=(x%p+p)%p;

3.费马小定理
模数为质数的时候: a p 1 1 ( m o d p ) a^{p-1}\equiv1(mod p)
即可知: a a p 2 1 ( m o d p ) a*a^{p-2}\equiv1(mod p)
然后直接快速幂


快速乘&快速幂

快速乘

ll ksc(ll a,ll b,ll p){
	ll res=0;
	while(b){
		if(b&1) res=(res+a)%p;
		a=(a+a)%p;
		b>>=1;
	}
	return res;
}

快速幂

ll ksm(ll a,ll b,ll p){
	ll res=1;
	while(b){
		if(b&1) res=ksc(res,a,p);
		a=ksc(a,a,p);
		b>>=1;
	}
	return res;
}

线性筛

pri 是素数集,mx是最大质因数,mn 是最小质因数,phi 是欧拉函数,mark 是标记一个数是否为素数(是的话,mark=0)
特别的,phi[1]=1

inline void linear_sieves(){
	mark[1]=1;phi[1]=1;
	for(int i=2;i<=N;++i){
		if(!mark[i]){pri[++num]=i;mn[i]=i;mx[i]=i;phi[i]=i-1;}
		for(int j=1;j<=num&&pri[j]*i<=N;++j){
			int now=pri[j]*i;
			mark[now]=1;
			mn[now]=pri[j];mx[now]=mx[i];
			if(i%pri[j]==0){
				phi[now]=phi[i]*pri[j];
				break;
			}
			else phi[now]=phi[i]*(pri[j]-1);
		}
	}
}

扩展欧几里得

对于任意的两个数x,y,求 a x + b y = g c d ( a , b ) ax+by=gcd(a,b) 的解
扩展到更一般的情况,求 a x + b y = c ax+by=c 也是可以的
但这个式子有解当且仅当 g c d ( a , b ) c gcd(a,b)|c

(平时我们用的调整,只在gcd(a,b)==1的时候适用,所以不管怎么样,全部先除以gcd(a,b),然后该怎么搞就怎么搞)

void exgcd(ll a,ll b,ll &x,ll &y){
	if(!b){
		x=1;y=0;return;
	}
	exgcd(b,a%b,y,x);
	y-=a/b*x;
}

质因数分解

一个数大于 x \sqrt{x} 的质因数最多只有一个
所以筛到 x \sqrt{x} 就够了

inline void divide(int x){
	for(int i=1;i<=num&&pri[i]*pri[i]<=x;++i){
		while(x%pri[i]==0){
			x/=pri[i];
			cnt[pri[i]]++;
		}
	}
	if(x!=1) cnt[x]++;
}

组合数

组合数的一般求解公式:
C n m = n ! ( n m ) ! m ! C_n^m=\frac{n!}{(n-m)!*m!}
组合数必备三条公式:
C n m = C n m 1 C_n^m=C_n^{m-1}
C n m = C n 1 m 1 + C n 1 m C_n^m=C_{n-1}^{m-1}+C_{n-1}^{m}
C n 0 + C n 1 + C n 2 + C n 3 . . . + C n n = 2 n C_n^0+C_n^1+C_n^2+C_n^3...+C_n^n=2^n

常见求解方法:
1.杨辉三角形

	for(int i=0;i<2000;++i) c[i][0]=c[i][i]=1;
	for(int i=1;i<2000;++i)
		for(int j=1;j<i;++j)
			c[i][j]=(c[i-1][j]+c[i-1][j-1])%;

2.线性筛出阶乘和逆元,O(1) 求组合数
fac 是阶乘,ifac 是阶乘的逆元

inline void init(){
	fac[0]=1;
	for(int i=1;i<=n;++i) fac[i]=(fac[i-1]*1ll*i)%P;
	ifac[n]=ksm(fac[n],P-2);
	for(int i=n-1;i>=1;--i) ifac[i]=ifac[i+1]*1ll*(i+1)%P;
}
inline ll C(ll n,ll m){	return fac[n]*1ll*ifac[n-m]%P*ifac[m]%P;}

高斯消元

如果无解就返回-1
无穷解返回0
有解返回1,并输出一组解

inline int gauss(){
	int i,j,k;
	for(i=1;i<=n;++i){
		k=i;
		for(j=i+1;j<=n;++j) if(fabs(g[k][i])<fabs(g[j][i])) k=j;
		for(j=1;j<=n+1;++j) swap(g[k][j],g[i][j]);
		for(j=i+1;j<=n;++j)
			for(int p=i+1;p<=n+1;++p)
				g[j][p]-=g[j][i]*g[i][p]/g[i][i];
	}
	int fg=0;
	for(i=1;i<=n;++i){
		fg=0;
		for(j=i;j<=n;++j)	if(g[i][j]) fg=1;
		if(g[i][n+1]&&fg==0) return -1;
		if(g[i][n+1]==0&&fg==0) return 0;
	}
	for(i=n;i>=1;--i){
		for(j=i+1;j<=n;++j)
			g[i][n+1]-=ans[j]*g[i][j];
		ans[i]=g[i][n+1]/g[i][i];
	}
}

矩阵快速幂

这玩意儿用处可多了

struct matrix{
	ll a[N][N];
	matrix(int t=0){
		memset(a,0,sizeof(a));
		for(int i=1;i<=n;++i) a[i][i]=t;//为了方便地处理单位矩阵 
	}
	inline matrix operator *(const matrix &y){
		matrix res(0);
		for(int i=1;i<=n;++i)
			for(int j=1;j<=n;++j)
				for(int k=1;k<=n;++k)
					res.a[i][j]=(res.a[i][j]+(a[i][k]%P*y.a[k][j])%P)%P;
		return res;
	}
	friend inline matrix operator ^(matrix x,ll b){
		matrix res(1);
		while(b){
			if(b&1) res=res*x;
			x=x*x;
			b>>=1;
		}
		return res;
	}
}

康拓展开
void reverse_contor(ll x){
	x--;memset(vis,0,sizeof(vis));
	int i,j;
	for(i=1;i<=n;++i){
		int t=x/fac[n-i];
		for(j=1;j<=n;++j)
			if(!vis[j]){
				if(!t) break;
				t--;
			}
		b[i]=j;
		vis[j]=1;
		x%=fac[n-i];
	}
}
ll contor(){
	ll res=0;
	for(int i=1;i<=n;++i){
		int t=0;
		for(int j=i+1;j<=n;++j) if(a[j]<a[i]) t++;
		res+=1ll*t*fac[n-i];
	}
	return res;
}

猜你喜欢

转载自blog.csdn.net/weixin_42557561/article/details/83796372