数论(不)全家桶

高斯消元

const double zero=1e-6;//注意精度
int n;
double a[maxn][maxn];
int gs(){
	int c,line,pai=0;
	for(c=1;c<=n;c++){
		line=c;
		for(int i=1;i<=n;i++){//找第i排第c项最大的 ,从1开始可以避免前面被忽略的 
			if(fabs(a[i][i]>zero)&&i<c) continue;//  该位有数且在第c行前就跳过 
			if(fabs(a[line][c])<fabs(a[i][c])) line=i;
		} 
		for(int i=1;i<=n+1;i++) swap(a[line][i],a[c][i]);
		if(fabs(a[c][c])<zero) continue;
		for(int i=n+1;i>=c;i--) a[c][i]=a[c][i]/a[c][c];//将(c,c)消为1 
		for(int i=c+1;i<=n;i++)
		  for(int j=n+1;j>=c;j--)
		    a[i][j]-=a[c][j]*a[i][c];//消掉所有行的c位 
		pai++;//解出的行数 
	}
	for(int i=n;i>=1;i--)
		for(int j=i-1;j>=1;j--) a[j][n+1]-=a[i][n+1]*a[j][i],a[j][i]=0;//消元得解		
	if(pai<n){//解不够 
		for(int i=1;i<=n;i++)
		  if(fabs(a[i][i])<zero&&fabs(a[i][n+1])>zero) return 1;//(≠0)=0,无解 
		return 2; //无穷解 0=0
	}
	else{
		for(int i=1;i<=n;i++){
			if(fabs(a[i][n+1])<zero) printf("x%d=0\n",i);
			else printf("x%d=%.2lf\n",i,a[i][n+1]);
		}
		return 0;
	}
}
}
const double jd=1e-6;
double a[110][110];
bool solve(){
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++) if(fabs(a[i][i])<fabs(a[j][i])&&a[j][j]!=1) swap(a[i],a[j]);//注意应从1开始 
		if(fabs(a[i][i])<jd) continue;
		for(int j=n+1;j>=i;j--)
		  a[i][j]/=a[i][i];
		for(int j=i+1;j<=n;j++)
		  for(int k=n+1;k>=i;k--) 
		    if(a[j][i])
		  a[j][k]/=a[j][i],a[j][k]-=a[i][k];
	}
	for(int i=n;i>=1;i--)
	  for(int j=i-1;j>=1;j--)
	    {
			a[j][n+1]-=a[i][n+1]*a[j][i];
	    	a[j][i]=0;
		}
	for(int i=1;i<=n;i++)
		if(fabs(a[i][i])<jd&&fabs(a[i][n+1])>jd){
			cout<<-1;
			return 0;
		} 
	for(int i=1;i<=n;i++)
	  if(fabs(a[i][i])<jd&&fabs(a[i][n+1])<jd){
	  	cout<<0;
	  	return 0;
	  }
	return 1;
}

线性筛

bool is_not_prime[maxn]={1,1};//0为是prime,1为不是prime 
int prime[maxn];
void xxs(int n){
	for(int i=2;i<=n;i++){//从2开始推 
		if(!is_not_prime[i]) prime[++prime[0]]=i;//是prime,加入prime数组 
		for(int j=1;j<=prime[0]&&i*prime[j]<=n;++j){//筛掉prime[j]的i倍(不是素数) 
			is_not_prime[i*prime[j]]=1;
			if(i%prime[j]==0) break;//已筛过,避免重复筛 
		}
	}
}

欧拉函数

求小于等于某个数的范围内与该数互质的数的个数

求单个数

int eular_phi(int n) {
	int m=sqrt(n+0.5);//防止精度问题
	int ans=n;
	for(int 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);//可能n最后为素数 
	return ans;
}

求范围内数的欧拉函数

bool is_not_prime[maxn];//0为是prime,1为不是prime 
int prime[maxn],phi[maxn];
void eu_phi(int n){
	phi[1]=1;
	for(int i=2;i<=n;i++){
		if(!is_not_prime[i]){//是素数 
			prime[++prime[0]]=i;
			phi[i]=i-1;//性质 
		} 
		for(int j=1;j<=prime[0]&&i*prime[j]<=n;++j){
			is_not_prime[i*prime[j]]=1;
			if(i%prime[j]==0){
				phi[i*prime[j]]=prime[j]*phi[i];//变形 
				break;
			}
			else phi[i*prime[j]]=(prime[j]-1)*phi[i];//变形 
		}
	}
}

欧拉函数求约数个数

bool is_not_prime[maxn];//0为是prime,1为不是prime 
int prime[maxn],num[maxn],d[maxn],n,ans;//d[]记录约数个数,num[]记录最小质因数的次幂 
void eu_phi(int n){
	d[1]=1;
	for(int i=2;i<=n;i++){
		if(!is_not_prime[i]){//是素数 
			prime[++prime[0]]=i;
			num[i]=1;//最小质因子就为其本身,次数为1 
			d[i]=2;//约数只有两个 
		} 
		for(int j=1;j<=prime[0]&&i*prime[j]<=n;++j){
			is_not_prime[i*prime[j]]=1;
			if(i%prime[j]==0){
				d[i*prime[j]]=d[i]/(num[i]+1)*(num[i]+2);//i的最小质因子为prime[j],i*prime[j]的最小质因子也为prime[j],次数为num[i]+1 
				num[i*prime[j]]=num[i]+1;//d[i]=(1+r1)*(1+r2)*...*(1+rk),d[i*prime[j]]=(1+r1+1)*...*(1+rk) →→d[i*prime[j]]=d[i]/(1+num[i])*(1+num[i]+1) 
				break;
			}
			else{
				d[i*prime[j]]=d[i]*2;//不能整除则之前不含prime[j],由于j从小到大枚举,prime[j]最小,所以d[i*prime[j]]=d[i]*(1+1) 
				num[i*prime[j]]=1;
			}
		}
	}
}

扩展欧几里得

例题

int exgcd(int x_1,int y_1,int &x_2,int &y_2){
	if(!y_1){
		x_2=1;
		y_2=0;
		return x_1;
	}
	int a,b;
	int s=gcd(y_1,x_1%y_1,a,b);
	x_2=b;
	y_2=a-x_1/y_1*b;
	return s; 
}

乘法逆元

扩展欧几里得

#include<bits/stdc++.h>
using namespace std;
int a,b,x,y;
int exgcd(int a,int b,int &x,int &y){
	if(b==0){
		x=1;
		y=0;
		return a;
	}
	int kk=exgcd(b,a%b,x,y);
	int tt=x;
	x=y;
	y=(tt-a/b*y);
	return kk;
}
int main(){
	std::ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>a>>b;
	for(int i=1;i<=a;i++){
		exgcd(i,b,x,y);
		cout<<((x%b)+b)%b<<'\n';
	}
	return 0;
}

线性求1~n

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int p,inv[3000001],n;
int main(){
	std::ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>p;
	inv[1]=1;
	cout<<1<<'\n';
	for(int i=2;i<=n;i++){
		inv[i]=(ll)(p-p/i)*inv[p%i]%p;
		cout<<inv[i]<<'\n';
	}
	return 0;
}

扩展欧拉

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int a,m,b,phi,len;
string ge;
int gcd(int x,int y){
	if(x<y) swap(x,y);
	if(y==0) return x;
	return gcd(y,x%y);
}
int qpow(int x,int y){
	int ans=1;
	while(y){
		if(y&1) ans=1ll*ans*x%m;
		x=1ll*x*x%m;
		y>>=1;
	}
	return ans;
}
inline int read()
{
	int ans=0;
	for(int i=0;i<(int)ge.size();i++){
		ans=(ans*10+ge[i]-'0')%phi;
	}
	return ans;
}
inline int read1()
{
	int ans=0;
	for(int i=0;i<(int)ge.size();i++){
		ans=(ans*10+ge[i]-'0');
	}
	return ans;
}
int phiq(int x){
	int ans=x;	
	for(int i=2;i*i<=x;i++){
		if(x%i==0){
			ans=ans/i*(i-1);
			while(x%i==0) x/=i;
		}
	}
	if(x>1) ans=ans/x*(x-1);
	return ans;
}
int main(){
	cin>>a>>m;
	int g=gcd(a,m);
	phi=phiq(m);
	int kk=phi;
	while(kk){
		len++;
		kk/=10;
	}
	cin>>ge;
	if(g==1) b=read();//满足gcd(a,m)==1则a^b%m==a^(b%phi(m))%m 
	else if((int)ge.size()>len){//若b>=phi(m)则a^b%m==a^(b%phi(m)+phi(m))%m 
		b=read();
		if(g!=1) b+=phi;
	} 
	else{
		b=read1(); 
		if(b>=phi) b=b%phi+phi;//若b>=phi(m)则a^b%m==a^(b%phi(m)+phi(m))%m 
		//若b<phi(m)则不作处理,且b不大 
	}
	cout<<qpow(a,b);
	return 0;
}

卢卡斯定理

用于超大数组合数取模,模数必须是素数

#include<bits/stdc++.h>
#define int long long
using namespace std;
int t,n,m,a,p;
int qpow(int x,int y,int mod){
	int ans=1;
	while(y){
		if(y&1) ans=1ll*x*ans%mod;
		x=1ll*x*x%mod;
		y>>=1;
	}
	return ans;
}
int C(int x,int y,int mod){
	if(x<y) return 0; 
	int jx=1,jy=1,jz=1;
	for(int i=1;i<=x;i++) jx=1ll*jx*i%mod;
	for(int i=1;i<=y;i++) jy=1ll*jy*i%mod;
	for(int i=1;i<=x-y;i++) jz=1ll*jz*i%mod;
	return 1ll*jx*qpow(jy,mod-2,mod)%mod*qpow(jz,mod-2,mod)%mod;
}
int lucas(int x,int y,int mod){
	if(!y) return 1;
	return 1ll*C(x%mod,y%mod,mod)*lucas(x/mod,y/mod,mod)%mod;
}
signed main(){
	cin>>t;
	while(t--){
		cin>>n>>m>>p;
		cout<<lucas(n+m,n,p)<<'\n';
	}	
	return 0;
}

中国剩余定理(CRT)

用于求解多模数意义下的线性同余方程,要求模数互质。
流程:
令余数为\(r\),模数为\(m\)

  • 1.模数求积\(S\)
  • 2.令\(C_i\)为第i个模数\(S/m_i\) 的商,并求出逆元
  • 3.\(ans=\sum_{i=1}^n r_i*C_i*C_i^{-1} ~mod~S\)
#include<bits/stdc++.h>
#define int __int128
using namespace std;
typedef long long ll;
ll n,r[11],m[11];
int ans; 
void exgcd(int a,int b,int &x,int &y){
	if(b==0){
		x=1;
		y=0;
		return;
	}
	exgcd(b,a%b,x,y);
	int kk=x;
	x=y;
	y=kk-a/b*y;
}
signed main(){
	cin>>n;
	__int128 S=1;
	for(int i=1;i<=n;i++){
		cin>>m[i]>>r[i];
		S*=m[i];
	}
	for(int i=1;i<=n;i++){
		int ny,y,c=S/m[i];
		exgcd(c,m[i],ny,y);
		ans=(ans+r[i]*c*ny%S)%S;
	}
	cout<<(ll)((ans%S+S)%S);
	return 0;
}

扩展CRT

扩展欧几里得求同余方程
设两个方程分别是 \(x\equiv a_1 \pmod {m_1}、x\equiv a_2 \pmod {m_2};\)

将它们转化为不定方程:\(x=m_1p+a_1=m_2q+a_2\),其中 \(p, q\) 是整数,则有 \(m_1p-m_2q=a_2-a_1。\)
由 裴蜀定理,当 $a_2-a_1 $不能被 \(\gcd(m_1,m_2)\) 整除时,无解;
其他情况下,可以通过 扩展欧几里得算法 解出来一组可行解 \((p, q)\)
则原来的两方程组成的模方程组的解为 \(x\equiv b\pmod M\),其中 \(b=m_1p+a_1\)\(M=\text{lcm}(m_1, m_2)\)

#include<bits/stdc++.h>
#define int __int128
using namespace std;
typedef long long ll;
ll n,r[100001],m[100001];
int ans; 
int exgcd(int a,int b,int &x,int &y){
	if(b==0){
		x=1;
		y=0;
		return a;
	}
	int g=exgcd(b,a%b,x,y);
	int kk=x;
	x=y;
	y=kk-a/b*y;
	return g;
}
int a,b,x,y;
signed main(){
	while(scanf("%lld",&n)!=EOF){
		memset(r,0,sizeof(r));
		memset(m,0,sizeof(m));
		for(int i=1;i<=n;i++) cin>>m[i]>>r[i];
		a=1,b=0;
		bool p=0;
		for(int i=1;i<=n;i++){
			int g=exgcd(a,m[i],x,y);
			if((b-r[i])%g){
				cout<<-1<<'\n';
				p=1;
				break;
			}
			x=(b-r[i])/g*x;
			b=b-a*x;
			a=m[i]/g*a;
			b=(b%a+a)%a;
		}
		if(!p) cout<<(ll)((b%a+a)%a)<<'\n';
	}
	return 0;
}
posted @ 2025-08-09 15:14  _dlwlrma  阅读(6)  评论(0)    收藏  举报