首页 > 其他分享 >基础数论大杂烩

基础数论大杂烩

时间:2024-01-27 11:44:23浏览次数:28  
标签:gcd 求解 数论 ll 基础 大杂烩 int equiv mod

exgcd

一,前置知识:

裴蜀定理:若有\(a,b\)且\(a,b\)不全为0,则存在整数\(x,y\),使得\(ax+by=gcd(a,b)\)。

二,算法过程:

作用 :给定\(a,b,c\),求解\(ax+by=c\)的整数解。

我们先考虑求解\(ax+by=gcd(a,b)\)。

由于\(gcd(a,b)=gcd(b,a\%b)\),所以有:

\(\begin{cases}ax_1+by_1=gcd(a,b) \\ bx_2+(a\%b)y_2=gcd(b,a\%b)\end{cases}\)

联立,得到:\(ax_1+by_1=bx_2+(a\%b)y_2\)

由于\(a\%b=a-b\cdot\lfloor\frac{a}{b}\rfloor\)。

化简后得到:\(ax_1+by_1=ay_2+b(x_2-\lfloor\frac{a}{b}\rfloor y_2)\)

所以说\(x_1=y_2,y_1=(x_2-\lfloor\frac{a}{b}\rfloor y_2)\)

递归求解!!!

边界条件:当\(b=0\)时,\(x=1,y=0\)。

求出来了方程:\(ax+by=gcd(a,b)\),怎么求\(ax+by=c\)呢?

已知:\(ax+by=gcd(a,b)\),则:\(c\cdot\frac{a}{gcd(a,b)}x+c\cdot\frac{b}{gcd(a,b)}y=c\)

当\(c|gcd(a,b)\)时,可以得到整数解,方程化简为\(ax_1+by_1=c\)。

以此可以判断是否有解(gcd在求exgcd时顺便求出)

那么,怎么求出最大、最小正整数解呢。

令\(x_1=\frac{c}{gcd(a,b)}\cdot x,y_1=\frac{c}{gcd(a,b)}\cdot y,a_1=\frac{a}{gcd(a,b)},b_1=\frac{b}{gcd(a,b)}\)。

可得,\(a(x_1+kb_1)+b(y_1-ka_1)=c\)。

所以通解就为:

\(\begin{cases}x=x_1+kb_1\\ y=y_1-ka_1\end{cases}\)

要求\(x,y\)的正整数解的个数,最大、最小正整数\(x,y\)。求出\(k\)的范围即可。

\(\begin{cases}x_1+kb_1>0 \\ y_1-ka_1>0\end{cases}\)

化简得:

\(\frac{-x_1}{b_1}<k<\frac{y_1}{a_1}\)

由于\(k\)为整数,那么\(\lfloor \frac{-x_1}{b_1} \rfloor <k< \lceil \frac{y_1}{a_1} \rceil\)

求解\(k\)即可。(注意c++中的精度问题)。

若没有合法的\(k\),则代表不存在一组正整数解,否则利用通解即可。

代码:(P5656 【模板】二元一次不定方程
#include<bits/stdc++.h>
#define ll long long
using namespace std;

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

int main(){
	int T;
	scanf("%d",&T);
	ll a,b,c;
	while(T--){
		scanf("%lld%lld%lld",&a,&b,&c);
		ll x,y;
		ll g=exgcd(a,b,x,y);
		//无解: 
		if(c%g){
			printf("-1\n");
			continue;
		}
		x=c/g*x,y=c/g*y;//求出来了一组给定的方程的一组特解。
		a/=g,b/=g;
		ll l=floor((double)-x/b),r=ceil((double)y/a);
		l++,r--;
		if(l<=r){//有正整数解 
			printf("%lld %lld %lld %lld %lld\n",r-l+1,x+l*b,y-r*a,x+r*b,y-l*a);
		}
		else{
			printf("%lld %lld\n",x+l*b,y-r*a);
		}
	} 
	return 0;
} 

参考资料:TJ-P5656

逆元

一,定义:

同余方程\(ax\equiv 1(mod~p)\)的解\(x\),被称为\(a\)的逆元,记作\(a^{-1},inv(a)\)

二,使用exgced求解逆元:

求解上述方程实际上就是在找\(ax-py=1\)的解,求解即可。(注意,若\(\gcd(a,p) \neq1\),则无解)。

三,使用费马小定理求解逆元:

费马小定理:若\(p\)为质数,那么\(a^{p-1}\equiv 1(mod~p)\)

此时\(inv(a)=a^{p-2}\)(快速幂求解)。

三,求解\(1!,2!,...n!\)的逆元:

有递推式:\(inv(n!)=(n+1)\cdot inv((n+1)!)\)

单独求出\(inv(n!)\),然后递推即可。

四,求解\(1,2,...,n\)的逆元:

法一:

有递推式:\(inv(i)=inv(i!)\cdot(i-1)!\)

先递推求出后面两个,然后递推求\(inv(i)\)

法二:

已知1的逆元为1。

设\(p=i*a+b~(0\leq b<a)\)

则\(i*a+b\equiv 0 (mod~p)\)

两边同时乘上\(i^{-1},b^{-1}\),得\(i^{-1}+a*b^{-1}\equiv 0~(mod~p)\)

所以,\(i^{-1}\equiv-a*b^{-1}~(mod~p)\)

而\(a=\lfloor\frac{p}{i}\rfloor,b=p\%i\)

所以\(i^{-1}\equiv-\lfloor\frac{p}{i}\rfloor*(p\%i)^{-1}~(mod~p)\)

由于\(p\%i<i\),所以可以递推求得。

为了使得逆元为正整数,所以先加上一个\(p\)再模去

所以\(i^{-1}=(p-\lfloor\frac{p}{i}\rfloor)*(p\%i)^{-1}\)

代码:(P3811 【模板】模意义下的乘法逆元
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll n,p;
const int N=3000000+5;
ll inv[N];
int main(){
	scanf("%lld%lld",&n,&p);	
	inv[1]=1;
	for(int i=2;i<=n;i++) inv[i]=inv[p%i]*(p-p/i)%p; 
	for(int i=1;i<=n;i++) printf("%lld\n",inv[i]);
	return 0;
} 

五,求解\(a_1,a_2,...a_n\)的逆元:

设\(s_i=\prod \limits_{j=1}^{i}a_j\)

显而易见:

\[\begin{align} &s_{i-1}^{-1}=s_i^{-1}\cdot a_i\\&a_i^{-1}=s_i^{-1}\cdot s_{i-1}\end{align} \]

用费马小定理求解\(s_n^{-1}\),然后递推即可。

代码:(P5431 【模板】模意义下的乘法逆元 2
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5e6+6;
int n;
ll p,k,a[N];
ll jc[N],inv_jc[N];
inline void read(ll &x){
    char ch=getchar();ll f=1;
    while(ch<'0'||ch>'9'){
    	if(ch=='-') f=-1;
    	ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10+(ch-'0');
		ch=getchar();
	}
	x*=f;
}
inline ll ksm(ll a,ll b){//a^b
    ll ans=1;
    while(b){
        if(b&1) ans=(ans*a)%p;
        a=a*a%p;
        b>>=1;
    }
    return ans%p;
}
int main(){ 
    scanf("%lld",&n);read(p);read(k);
    jc[0]=1;
    for(int i=1;i<=n;i++){
        read(a[i]);
        jc[i]=(jc[i-1]*a[i])%p; 
    }
    inv_jc[n]=ksm(jc[n],p-2);
    for(int i=n;i>=1;i--) inv_jc[i-1]=(inv_jc[i]*a[i])%p;
    ll ans=0;
    ll kk=k%p;
    for(int i=1;i<=n;i++){
        ans=(ans+(((jc[i-1]*inv_jc[i])%p)*kk)%p)%p;
        kk=kk*k%p;
    }
    printf("%lld\n",ans);
    return 0;
} 

中国剩余定理(CRT)

一,形式:

\(\begin{cases} x\equiv a_1(mod~m_1)\\x\equiv a_2(mod~m_2)\\...\\x\equiv a_n(mod~m_n)\end{cases}\)

\(m_i\)互相互质。

二,公式:

设\(m=\prod \limits_{i=1}^{n}m_i,M_i=\frac{m}{m_i}~,~t_i\)表示\(M_i\)在模\(m_i\)意义下的逆元。

\(x\)的一组解为\(x=\sum_{i=1}^{n}a_iM_it_i\)

通解可表示为\(x+km\)。

(证明见证明:数论四大定理之中国剩余定理

三,求解:

核心在于求解\(t_i\)。

用\(exgcd\)求解\(t_i\cdot M_i+y\cdot m_i=1\)即可

代码:(P1495 【模板】中国剩余定理(CRT)

#include<bits/stdc++.h>
#define int __int128
using namespace std;

inline int read(){
	char ch=getchar();
	int x=0,f=1;
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	return f*x;
}
inline void write(int x){
	if(x<0) putchar('-'),x*=-1;
	if(x>9) write(x/10);
	putchar(x%10+'0');
}

int n,M=1;
const int N=12;
int a[N],m[N];
void exgcd(int a,int b,int &x,int &y){
	if(b==0){
		x=1,y=0;
		return;
	}
	exgcd(b,a%b,y,x);
	y-=a/b*x;
}
signed main(){	
	n=read();
	for(int i=1;i<=n;i++){
		m[i]=read(),a[i]=read();
		M*=m[i];
	}
	int ans=0,Mi;
	for(int i=1;i<=n;i++){
		int x,y;
		Mi=M/m[i];
		exgcd(Mi,m[i],x,y);
		ans=(ans+a[i]*Mi%M*x%M+M)%M;
	}
	write(ans);
	return 0;
} 

拓展中国剩余定理(exCRT)

一,形式:

\(\begin{cases} x\equiv a_1(mod~m_1)\\x\equiv a_2(mod~m_2)\\...\\x\equiv a_n(mod~m_n)\end{cases}\)

\(m_i\)不互相互质。

二,求解:

证明一:

我们假设已经求出来了前面\(k-1\)组的方程的最小整数解,\(x\)。

设\(m=lcm(m_1,m_2,...,m_{k-1})\)

则前面\(k-1\)组的方程的通解为\(x+km\)

考虑有一个整数\(t\),使得\(x+tm\equiv a_i~(mod~m_i)\)

化简后得:\(tm\equiv a_i-x~(mod~m_i)\)

相当于是在求方程\(tm+ym_i=a_i-x\)是否有解。

用exgcd求解即可。

若有解,则\(x^,=x+t\cdot m\)。

当第一个方程时,直接求解即可。

证明二:

详见:扩展中国剩余定理(扩展CRT)详解

代码:(P4777 【模板】扩展中国剩余定理
#include<bits/stdc++.h>
#define int __int128
using namespace std;

inline int read(){
    char ch=getchar();
    int x=0,f=1;
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return f*x;
}
inline void write(int x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
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,y,x);
    y-=a/b*x;
    return g;
}
const int N=1e5+5;
int a[N],m[N],n;
signed main(){  
    n=read();
    for(int i=1;i<=n;i++){
        m[i]=read(),a[i]=read();
    }
    int M=1,x,y,g,ans=0;
    for(int i=1;i<=n;i++){
        g=exgcd(M,m[i],x,y);
        x*=((a[i]-ans)/g);
        ans=ans+x*M;
        M*=(m[i]/g);
        ans=(ans%M+M)%M;
    }
    write(ans);
    return 0;
} 

BSGS

一,形式:

求解\(a^x\equiv b~(mod~p)\)

二,求解:

令\(t=\lceil sqrt(p)\rceil\)

则\(x=At-B~(0\le B<t~,~0<A\le t)\)

则\(a^{At-B}\equiv b~(mod~p)\)

移相,得:\(a^{At}\equiv b\cdot a^B~(mod~p)\)

枚举左右两边,用\(map\)判断是否有相等的即可。

代码:(P3846【模板】BSGS
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll p,b,n;
inline ll ksm(ll x,ll y){//x^y
	ll ans=1;
	while(y){
		if(y&1) ans=ans*x%p;
		x=(x*x)%p;
		y>>=1; 
	}
	return ans;
}
map<ll,ll> mp;
int main(){	
	scanf("%lld%lld%lld",&p,&b,&n);
	int t=sqrt(p)+1;
	ll bb=n%p;
	//枚举B: 
	for(int i=0;i<t;i++){
		mp[bb]=i;
		bb=(bb*b)%p;
	}
	ll czc=ksm(b,t);
	bb=1;
	//枚举A: 
	for(int i=1;i<=t;i++){
		bb=(bb*czc)%p;
		if(mp.find(bb)!=mp.end()){
			printf("%lld\n",t*i-mp[bb]);
			return 0;
		}
	}
	cout<<"no solution";
	return 0;
} 

Lucas定理

一,形式:

\(\tbinom{n}{m}\%p=\tbinom{n/p}{m/p}\tbinom{n\%p}{m\%p}\%p\)

适用于\(n,m\)较大,但\(p\)较小的情况下求解组合数

证明:算法学习笔记(25): 卢卡斯定理 - 知乎

二,求解:

我们利用lucas定理,递归求解即可。

注意边界条件,以及\(\tbinom{n\%p}{m\%p}\)中的\(n\%p,m\%p\)都在\([0,p-1]\),可以直接求解。

代码:(P3807 【模板】卢卡斯定理
#include<bits/stdc++.h>
#define ll long long 
using namespace std;
const int N=2e5+5;
ll n,m,p;
int T; 
ll jc[N];

inline ll ksm(ll a,ll b){//计算a^b 
	ll sum=1;
	while(b){
		if(b&1) sum=sum*a%p;
		a=a*a%p;
		b>>=1;
	}
	return sum;
}
inline ll C(ll x,ll y){//x选y 
	if(x<y) return 0;
	return (jc[x]*ksm(jc[y],p-2)%p*ksm(jc[x-y],p-2)%p+p)%p;
}
inline ll lucas(ll x,ll y){//x选y 
	if(!y) return 1;
	return (C(x%p,y%p)*lucas(x/p,y/p)%p+p)%p;
}

int main(){	
	scanf("%d",&T);
	while(T--){
		scanf("%lld%lld%lld",&n,&m,&p);
		jc[0]=1;
		for(int i=1;i<=n+m;i++) jc[i]=jc[i-1]*i%p;//注意,是n+m
		printf("%lld\n",lucas(n+m,n));
	}
	return 0;
} 

二项式反演

一,形式:

基本形式:

设\(f(x)\)表示\(x\)集合的交集的大小,\(g(x)\)表\(x\)个集合的补集的交集的大小

\[f(n)=\sum_{i=1}^n~(-1)^{i-1}~\tbinom{n}{i}~g(i)~\Leftrightarrow~ g(n)=\sum_{i=1}^n~(-1)^{i-1}~\tbinom{n}{i}~f(i) \]

常用形式1:

设\(f(x)\)表示恰好满足\(x\)个方案数量,\(g(x)\)表示最多满足\(x\)个方案数量

\[f(n)=~\sum_{i=0}^{n}~(-1)^{n-i}~\tbinom{n}{i}~g(i)~\Leftrightarrow~ g(n)=~\sum_{i=0}^{n}~\tbinom{n}{i}~f(i) \]

常用形式2:

设\(f(x)\)表示恰好满足\(x\)个方案数量,\(g(x)\)表示至少满足\(x\)个方案数量

\[f(k)=\sum_{i=k}^n~(-1)^{i-k}~\tbinom{i}{k}~g(i)\Leftrightarrow~ g(k)=\sum_{i=k}^n~\tbinom{i}{k}~f(i) \]

证明:二项式反演及其应用 - GXZlegend

(注:二项式反演可以拓展到多元的,详见:二项式反演-allenge高维反演的一个例子

欧拉筛求质数/欧拉函数

一,筛质数:

代码:

for(int i=2;i<=m;i++){
    if(!vis[i]) pri[++cnt]=i;
    for(int j=1;j<=cnt&&pri[j]*i<=m;j++){
        vis[pri[j]*i]=1;
        if(i%pri[j]==0) break;
    }
}

正确性/时间复杂度证明

二,求解欧拉函数

代码:

phi[1]=1;
for(ll i=2;i<N;i++){
    if(!vis[i]){
        pri[++cnt]=i;
        phi[i]=i-1;
    } 
    for(int j=1;j<=cnt&&i*pri[j]<N;j++){
        vis[i*pri[j]]=1;
        if(i%pri[j]==0){
            phi[i*pri[j]]=phi[i]*pri[j];
            break;
        }
        phi[i*pri[j]]=phi[i]*(pri[j]-1);
    }
}

正确性证明

标签:gcd,求解,数论,ll,基础,大杂烩,int,equiv,mod
From: https://www.cnblogs.com/123456xwd/p/17991265

相关文章

  • Linux基础命令笔记(黑马)
    Linux基础命令Linux常用快捷键ctrl+c:强制停止程序运行ctrl+d:退出用户登录或某些特定程序的专属页面(不能用于vim)!历史命令前缀:执行历史中最后使用带有该命令前缀的命令例:!p相当于python、!t相当于tailctrl+r:可输入历史命令关键字搜索到想要到命令,按回车直接执行,按左......
  • 1.JAVA基础-JDK的介绍
    Java语言语言:人与人交流沟通的表达方式。计算机语言:人与计算机之间进行信息交流沟通的一种特殊语言。Java语言是美国Sun公司(StanfordUniversityNetwork)在1995年推出的计算机语言。Java之父:詹姆斯·高斯林(JamesGosling)。Java语言的三个版本⚫JavaSE⚫JavaME......
  • python--pyQt 基础框架代码 pyside6
    importsysfromPySide6importQtWidgets,QtCore,QtGuifromPySide6.QtCoreimportQt,QRectfromPySide6.QtGuiimportQColor,QEnterEventfromPySide6.QtWidgetsimportQApplication,QDialog,QMainWindow,QGraphicsDropShadowEffectimportyiqi_uiclassMain......
  • 【JAVA基础】String、StringBuilder和StringBuffer的区别——巨详细
    先给答案String是不可变的,StringBuilder和StringBuffer是可变的。而StringBuffer是线程安全的,而StringBuilder是非线程安全的。源码先看看jdk1.8中关于String、StringBuilder和StringBuffer部分的源码,我们看某个类或者某个属性是否不可变首先要看修饰类的关键字是什么,final表示不可......
  • Linux基础命令-find
    目录Linux基础命令-find一、工作特点:二、常用参数:三、练习:Linux基础命令-find实时查找工具,通过遍历指定路径下的文件系统完成文件查找find命令的功能是用于根据给定的路径和条件查找相关文件或目录,参数灵活方便,且支持正则表达式,结合管道符后能够实现更加复杂的功能,是Linux系统......
  • 【scikit-learn基础】--『回归模型评估』之可视化评估
    在scikit-learn中,回归模型的可视化评估是一个重要环节。它帮助我们理解模型的性能,分析模型的预测能力,以及检查模型是否存在潜在的问题。通过可视化评估,我们可以更直观地了解回归模型的效果,而不仅仅依赖于传统的评估指标。1.残差图所谓残差,就是实际观测值与预测值之间的差值。......
  • Golang 语言入门:基础语法与示例
    引言Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。自2009年推出以来,Go已经成为云计算、微服务、网络服务器等领域的热门选择。其设计哲学是简洁、快速和易于理解,这使得Go语言特别适合当今快速发展的软件行业。Go语言的基本语法......
  • SQLCommon封装基础查询方法
    点击查看代码///<summary>///单一结果查询///</summary>///<paramname="sql"></param>///<returns></returns>publicstaticintExecuteNonQuery(stringsql){......
  • NanoFramework操作ESP32(一)_基础元器件篇(二11)_土壤湿度传感器
    编号名称功能1AO模拟输出2DO数字输出3GND电源地4VCC电源正......
  • NanoFramework操作ESP32(一)_基础元器件篇(二十九)_雨滴传感器
    一、元器件介绍  WaterSensor水位传感器是一款简单易用、性价比较高的水位/水滴识别检测传感器,其是通过具有一系列的暴露的平行导线线迹测量其水滴/水量大小从而判断水位。可使用模拟信号接收。1、针脚用途2、电气参数二、示例代码1、代码:元器件的针脚ESP32模块......