费马小定理与乘法逆元

RT,本文主要讲述费马小定理和乘法逆元的应用。

费马小定理

定义

对于质数p,则对于任意自然数a,有:

apa(modp)

证明

  1. a=0,此时同余式两边都是0,成立
  2. pa,此时同余式两边都是0,成立
  3. pa

由于p是素数,pa,故gcd(a,p)=1

引理10modp,amodp,2amodp(p1)amodp值互不相同

证明:反证法,设存在i,j0i<j<p,i,jZ,使得aiaj(modp)

移项可以得到a(ji)0(modp),此时说明p|a(ji)。但需要知道的是,gcd(a,p)=1,故a不含有质因子p

又因为p|a(ji),就可以推出p|(ji)。因为0i<j<p,所以1jip1

因为p是素数,这些数中不存在p的倍数。故假设不成立,原命题成立。

引理2amodp,2amodp(p1)amodp的值不重不漏地取遍了1p1

证明:由引理1可知,amodp,2amodp(p1)amodp的值互不相同,又因为对于任意整数k0kamodp<p,且0amodp0

所以amodp,2amodp(p1)amodpp1个不同的值,值域也是[1,p1]。所以集合{amodp,2amodp(p1)amodp}与集合{1,2,3,p1}构成一个双射,原命题成立。

由引理1和引理2可知:i=0p1ia(p1)!(modp),左式展开移项得到ap1(p1)!(p1)!(modp)

因为p是素数,所以p(p1)!。所以可以得到(ap11)(p1)!0(modp)ap11(modp)。同余式同乘a即可得到apa(modp)

QED.

应用

Sum

题意:给定n,设S(k)表示x1+x2+x3++xk=n的不定方程中,所有未知数都为正整数的解的个数。

i=1nS(k)mod(109+7)

分析:对于S(k),可以视作有n个一样的球和k个不一样的盒子,要求不可以有空盒的方案个数。

根据隔板法,有n1个空隙,需要选出k1个空隙插入板子,方案数为(n1k1)

所以所求即为i=1nS(k)=i=1n(n1i1)=i=0n1(n1i)

对于这个式子,其等价于2n1,证明如下:

组合证明

从组合意义上,为对从n1个球中拿出0,1,2,3n1个球的方案数总和。

考虑任意一种拿球的方案,随便拿什么球,显然拿的球的个数k[0,n1]。则这个方案归属于拿出k个球的方案之一。

所以对于任意一种拿球的方案,都可以将其规为对从n1个球中拿出0,1,2,3n1个球中的每一类。

综上所述,对从n1个球中拿出0,1,2,3n1个球的方案与任意拿球的方案构成一个双射,且后者显然是2n1种方案(可选可不选)。

QED.

代数证明

二项式定理可知:

i=0n1(n1i)=i=0n11i1ni1(n1i)=(1+1)n1=2n1

QED.

由于n101000000,所以可以对n当作字符串处理,边加边对109+6取模,最后快速幂即可。

时间复杂度:O(log10n)

#define int long long
int a[1050050];
char s[1005050];
const int mod=1e9+7,p=1e9+6;
int power(int a,int b){
	int ans=1;
	while(b){
		if(b&1)ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ans;
}
signed main(){
	while(cin>>s){
		int bit=strlen(s);
		int n=0;
		for(int i=0;i<bit;i++){
			n=n*10+(s[i]-'0');n%=p;
		}
		cout<<power(2,n-1)<<"\n";		
	}
}

Ignatius's puzzle

题意简述:设f(x)=5x13+13x5+kax,其中a为未知数,k为给定的正整数。

求最小的a的取值,使得对于任意正整数x65|f(x)

解:

由算术基本定理可知,证明65|f(x)等价于求证5|f(x)13|f(x)

先来看第一部分:5|f(x)

由于5|5x13,所以f(x)13x5+kax0(mod5),因为5是质数,所以可以得到13x513x(mod5)。回带即可得到(13+ak)x0(mod5)

此式对所有正整数x成立,所以可以假定gcd(x,5)=1,若不互质则此式显然成立。

故可以得到ak13(mod5)ak2

所以5|f(x)的充要条件就是ak2(mod5)
类似地可以得到13|f(x)的充要条件是ak8(mod13)

由不定方程解的性质可知,若这两个方程同时有解,则最小正值a一定不超过65。直接枚举即可。

用裴蜀定理判断一下无解即可。

int main(){
	while(cin>>k){
		if(k%5==0||k%13==0)cout<<"no\n";
		else {
			for(int x=0;x<=65;x++){
				if(x*k%5==2&&x*k%13==8){
					cout<<x<<"\n";break;
				}
			} 
		}
	}
	return 0;
}

集合计数

这个题很妙。

先将k个元素选出来,也即这k个位置固定,方案数(nk)

然后只需要考虑剩下的nk个元素。

g(i)表示交集元素数量至少为i个的情况。设f(i)表示刚好为i个的情况。所求为f(0)

则容易得到:

g(i)=j=ink(nkj)f(j)

根据二项式反演定理可以得到:

f(0)=i=0nk(1)i(nki)g(i)

问题转化为求g

由于固定了i个位置,选k的时候还固定了k个,所以还剩下2nki个集合。

这些集合取交集这i个元素都是合法的,但不能取空集。所以有方案数22nki1

g(i)=22nki1

用费马小定理给它把指数模一下即可。

所以最终答案为:

f(0)=i=0nk(1)i(22nki1)(nki)

乘法逆元

定义

ax1(modp),则xa在模p意义下的逆元。

根据裴蜀定理,a存在模p意义下的逆元的充要条件是gcd(a,p)=1.。做题时需要注意这个点。

意义

在模意义下,除法非常麻烦,因为模运算一般不会出现小数。

比如对于mamodp,不可能对小数进行取模,所以就有了乘法逆元。设ax1(modp)

mamodp=m·1amodp。又因为a·1aax1(modp)。所以mamx(modp)

求法

根据裴蜀定理,a存在模p意义下的逆元的充要条件是gcd(a,p)=1.。做题时需要注意这个点。

扩展欧几里得

适用于存在乘法逆元的情况。ax1(modp)本身就是一个线性同余方程,食用扩展欧几里得进行求解即可。

费马小定理

适用于p是素数的情况。我们都知道apaa·ap21(modp)

所以ap2就是a在模p意义下的一个逆元。

黑科技

inva表示a在模p意义下的逆元。
设我们需要求a1,a2an的逆元,这个黑科技可以做到O(n+logp)

si=j=1iai,设H=invsn,这一步是O(n+logp)的。

然后设hi=Hk=i+1nak,则invai=hi·si1

证明显然。

递推

以上两种做法都可以在O(logn)的时间内求出单个数的乘法逆元,但这远远不够。有时候我们需要求出1n每一个数的乘法逆元,这时候就需要递推法了。

inva(ppa)·invpmoda

证明:

t=pa,p=at+r(0r<a)

则有at+r0(modp)atr(modp)。两边同时乘inva得到tr·inva(modp)。移项得到invat·invr(modp)。负数化正得到invapt·invr

根据模运算的性质,r=pmodi。带入即可得到:

inva(ppa)·invpmoda

性质

  1. 倒数具有的性质逆元同样具有
  2. 所有合法的inva,其模p的值都相等。也即设invaa逆元中的最小正值,则所有合法的逆元全部可以表示为kp+inva,kZ,0inva<p

证明:根据扩展欧几里得法求逆元的过程和二元一次不定方程解的通项公式可知

应用

Sumdiv

首先因为9901是质数,所以可以考虑拆开。

f(A)A的约数和。

A质因数分解后为p1c1p2c2pmcm,则对于这些数的任意组合都是A的约数,根据乘法原理可以得到:

f(A)=i=1m(k=1cipik)

括号拆开就可以得出所有的因数。

运用等比数列求和公式,我们知道:

f(A)=i=1mpici+11pi1

需要注意的是,有可能9901|(pi1),此时没有逆元,但容易知道pip后的贡献为ci+1,证明考虑对于每一个pik拆开为(x9901+1)k,其模9901的值显然为1.

那么f(AB)就是令所有的ci=ciB即可。

#define int long long
int a,b,p=9901,n,c[100500],tot,cnt[100500],ans;
inline int power(int a,int b){
	int ans=1,t=0;
	while(b>=1){
		++t;
		if(b&1)ans=1ll*ans*a%p;
		a=1ll*a*a%p;
		b>>=1;
		if(!b)break;
	}
	return ans;
}
inline void get(int x){
	for(int i=2;i<=x;++i){
		if(x%i==0){
			c[++tot]=i;
			while(x%i==0)x/=i,cnt[tot]++;
			cnt[tot]*=b;cnt[tot]++;
		}
	}
}
void solve(){
	get(a);ans=1;
	for(int i=1;i<=tot;i++){
	//	cout<<c[i]<<" "<<cnt[i]<<"\n";
		if((c[i]-1)%p==0){
			ans*=cnt[i];
		} 
		else ans=1ll*ans*(power(c[i],cnt[i])-1)%p*power(c[i]-1,p-2)%p;
	}
}
signed main(){
	ios::sync_with_stdio(false);
	read(a),read(b);solve();
	cout<<(ans%p+p)%p;//<<"\n"<<tot<<" "<<c[tot]<<" "<<cnt[tot];
}

排课表

考虑容斥。

显然总的组合方案数是(nm),第一节课选a节课中的其中一个的方案数是a(n1m1)

同理最后一节课选b的方案数是b(n1m1)。然后第一节课选a中一个,最后一节课选b中一个,方案数是ab(n2m2)

所以总的方案数是:

(nm)(a+b)(n1m1)+ab(n2m2)=n!(a+b)(n1)!+ab(n2)!(nm)!

字符串

感觉很妙。

仍然显然是容斥。

总的序列个数显然是(n+mm),考虑一个不合法序列所需要的特征:

肯定有一个最小的位置k=2x+1,使得[1,k]不合法。

考虑限定这个位置,则剩下的[k+1,n+m]随便取,方案数是(n+m2x1mx1),前面的序列的方案数是(kx+1)。显然x可以取1n+m12。对其进行求和,设t=n+m12

则不合法的方案数是i=0t(n+m2i1mi1)(2ii)

预处理阶乘的话,这玩意可以在O(n)的复杂度内求出。

这就满足了吗?不,我们有更加优秀的做法。

从组合意义来讲,这个做法可以理解为,由于第k个位置一定为0,所以还剩下m1个0可以拿,直接从n+m个数里挑m1个0,剩下的一个随波逐流,也正好可以归为k的某一个取值中的某一类。由于不合法序列存在且仅存在一个位置,所以其实这个剩下的0是确定的,是m个零中的某一个,故方案数m·1m(n+mm1)=(n+mm1)

这里网上的题解是从双射的角度理解的,本质一样。

int n,m,jc[N];
inline int power(int a,int b){
	int ans=1;
	while(b){
		if(b&1)ans=ans*a%p;
		a=a*a%p;
		b>>=1;
	}
	return ans;
}
int C(int n,int m){
	return jc[n]*power(jc[m],p-2)%p*power(jc[n-m],p-2)%p;
}
signed main(){
	ios::sync_with_stdio(false);
	cin>>n>>m;
	jc[1]=1;for(int i=2;i<=n+m;i++)jc[i]=jc[i-1]*i%p;
	cout<<((C(n+m,m)-C(n+m,m-1))%p+p)%p;	
}

黑科技-密码

这里提炼出一个通法:设p是素数,gcd(a,p1)=1s为给定的值,定指数类高次同余方程的解法

也即求高次同余方程sta(modp)的值t

我们都知道,如果定t不定a,则可以通过BSGS算法根号内求解,

a不定t呢?

考虑对两边分别乘方k次,化为sktak(modp)

由费马小定理,可知sktakmod(p1)(modp)

那么令ak1(mod(p1)),此时sk就是一个解。

而由于gcd(a,p1)=1,所以不定方程ak+b(p1)=1有无穷组解,求出k的最小正值解即可。

而对于本题,a=230+3是一个质数,满足条件,直接求即可。

#define int long long
int p,a=(1<<30)+3,b,t,s;
int power(int a,int b,int p){
	int ans=1;
	while(b){
		if(b&1)ans=ans*a%p;
		a=a*a%p;
		b>>=1;
	}
	return ans;
}
int exgcd(int a,int b,int &x,int &y){
	if(!b){
		x=1,y=0;return b;
	}
	int d=exgcd(b,a%b,x,y);
	int z=x;x=y,y=z-(a/b)*y;return d;
}
signed main(){
	ios::sync_with_stdio(false);
	int T;cin>>T;
	while(T--){
		cin>>s>>p;
		int x,y;exgcd(a,p-1,x,y);
		x=(x%(p-1)+p-1)%(p-1);
		cout<<power(s,x,p)<<"\n";
	}
}

卡农

组合神题,不过是不是应该绛紫了

看着是黑题还不敢做,读完题发现很水……

首先如果从二进制的角度来说,将一个集合表示为一个二进制数,则其对应了[0,2n1]中的所有整数。

题目里的限制条件有:

  1. 片段互异——不重复选数
  2. 每个音阶奏响次数为偶数——选出的所有数异或后可以得到0
  3. 集合有序——统计无序答案,去掉m!即可

根据异或运算的性质,对于a1a2akx=0x=a1a2ak

则如果确定了前i个片段,第i+1个也随之确定,且唯一。

确定前i个片段的方案数是——A2n1i

每一个方案都唯一对应i+1的取值。

其中不合法的情况有:

  1. 这些片段里面本身异或得到0的,也即本身合法的
  2. 这些片段里i+1的取值出现过的。

fi表示选出i个数的合法方案数,则第一个问题方案数就是fi

仅需解决第二个问题。

由于i+1是确定的,那么重复的数也是确定的,所以不确定的位置只有i1

再者,重复数的位置也在1i之间的某个位置,进行枚举有i种可能性。

同时思考,本身异或和为0,从异或序列里删掉两个数,异或和同样为0.

反过来看,我们可以看作两个相同的数插入了异或序列,一个只能在最末尾,一个可以在1i的任意位置。

则原本异或序列存在fi1个,插入的数可以有2n1(i1)不同取法,每种取法有i种插入方法。

所以方案数为fi1(2ni)i

综上所述,可以得到:

fi+1=A2n1ififi1(2ni)i

显然f0=1,f1=0,因为不能不选。最终答案是fn·invn!mod(108+7)

注意到A2n1i中底数非常大,但由于其固定,所以可以预处理。

#define int long long
#define p 100000007
#define N 1000500
int m,A[N],f[N],n,num;
int power(int a,int b){
	int ans=1;
	while(b){
		if(b&1)ans=a*ans%p;
		a=a*a%p;
		b>>=1; 
	}
	return ans;
}
void init(){
	cin>>n>>m;
	A[0]=1;num=power(2,n)-1;
	for(int i=1;i<=m;i++){
		A[i]=A[i-1]*(num-i+1)%p;
		A[i]=(A[i]%p+p)%p; 
	} 
} 
void solve(){
	f[0]=1;
	for(int i=1;i<m;i++){
		f[i+1]=A[i]-f[i];
		if(i>0)f[i+1]-=f[i-1]*i%p*(num-i+1)%p;
		f[i+1]=(f[i+1]%p+p)%p;
	}
	for(int i=1;i<=m;i++)f[m]=f[m]*power(i,p-2)%p;
	cout<<f[m]<<"\n";
}
signed main(){
	ios::sync_with_stdio(false);
	init();solve();
	return 0;
}
posted @   spdarkle  阅读(170)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示