20级ACM队--初等数论

本文详细介绍了模运算的性质及其在计算中的应用,包括模运算的等价关系、欧几里得算法(辗转相除法)用于求最大公约数,以及扩展欧几里得算法在求乘法逆元中的作用。还探讨了欧拉函数的定义、性质和计算方法,并展示了如何使用线性复杂度求1~N的欧拉函数数值。此外,文章通过实例讲解了数论分块和组合计数在解决数学问题上的应用,如求解同余方程和组合数模的值。
摘要由CSDN通过智能技术生成

模运算

( a + b ) % p = ( a % p + b % p ) % p ( a + b ) \% p = ( a \% p + b \% p ) \% p (a+b)%p=(a%p+b%p)%p

( a ∗ b ) % p = ( ( a % p ) ∗ ( b % p ) ) % p ( a * b ) \% p = ( (a \% p ) * (b \% p ) ) \% p (ab)%p=((a%p)(b%p))%p

c ∗ ( a % p ) = ( c ∗ a ) % ( c ∗ p ) c * ( a \% p ) = ( c *a ) \% ( c *p ) c(a%p)=(ca)%(cp)

欧几里得算法

对于任意两个正整数 a,b ,都有:
a = k b + r ( k , r ∈ N ) a=kb+r (k,r∈N) a=kb+r(k,rN)

所以有:
r = a r=a%b r=a

然后我们假设 c 是 a 和 b 的最大公约数,即
c = g c d ( a , b ) c=gcd(a,b) c=gcd(a,b)

然后,我们就能得到:
c ∣ a 和 c ∣ b ( x ∣ y 表 示 x 能 够 整 除 y , y 能 被 x 整 除 , 也 就 是 y / x 是 整 数 ) c|a 和c|b (x|y 表示 x 能够整除 y , y能被x整除 , 也就是y/x是整数) cacbxyxyyx,y/x

然后又因为上面那个式子,有:
r = a − k b r=a−kb r=akb
c ∣ r c|r cr
c = g c d ( b , r ) c=gcd(b,r) c=gcd(b,r)

g c d ( a , b ) = g c d ( b , a % b ) gcd(a,b)=gcd(b,a\%b) gcd(a,b)=gcd(b,a%b)

而且
g c d ( a , 0 ) = a gcd(a,0) = a gcd(a,0)=a

辗转相除法函数代码:

int gcd(int a,int b)//就是欧几里得算法函数,即辗转相除法,求gcd(a,b)
{
	int c;
	while(b!=0)
	{
		c=a;
		a=b;
		b=c%b; 
	}
	int ans=a;
	return ans;
}

扩欧算法

这里写图片描述
这里写图片描述

例题1
求关于 x x x的同余方程 a x ≡ 1 ( m o d b ) a x \equiv 1 \pmod {b} ax1(modb) 的最小正整数解.输入数据保证一定有解

乘法逆元

这里写图片描述

算逆元的三个方法:

第一个方法:
这里写图片描述

inline void exgcd(LL a,LL b)//扩展欧几里得算法求乘法逆元
{
	if(b==0)
	{
		x=1,y=0;
		return ;
	}
	exgcd(b,a%b);
	LL k;
	k=x;
	x=y;
	y=k-(a/b)*y;
}

.
.
.
.

第二个方法:
这里写图片描述

int quick(int x,int p)//快速幂求乘法逆元,谨记,p是一个素数
{
	int ans=1;
	int d=p-2;
	while(d)
	{
		if(d%2==1)
		{
			ans*=x;
			ans%=p;
		}
		x*=x;
		x%=p;
		d/=2;
	}
	return ans;
}

.
.
.
.
第三个方法:
这里写图片描述

这里有一个模板题目 题目传送门

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
long long x,y,n,f[3000010];
void work(long long n,long long p)//线性求逆元,时间复杂度O(n) 
{
	f[1]=1;
	for(long long i=2;i<=n;i++)
	{
		f[i]=-(p/i)*f[p%i];
		f[i]=(f[i]%p+p)%p;
	}
}
int main()
{
	long long a,p,b,n,i;
	cin>>n>>p;
	work(n,p); 
	for(long long i=1;i<=n;i++)
	{
		printf("%lld\n",f[i]);//处理出   最小正整数!!
	}

	return 0;
}

欧拉函数

对于正整数n,欧拉函数是小于或等于n的正整数中与n互质的数的数目,记作φ(n).

注意,欧拉函数是一个积性函数,只要M,N互质,就可以直接φ(MN)=φ(M)*φ(N),在线性求1~N的欧拉函数数值的时候很有用。
另外:若a,b互质且f(ab)=f(a)*f(b),则f(x)是积性函数
若a,b不互质且也有f(ab)=f(a)*f(b),则f(x)是完全积性函数

在这里插入图片描述

#include<iostream>
#include<cstdio>
#include<cstring>
#define LL long long
using namespace std;

LL n;
LL phi(LL x)
{
	LL 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()
{
	LL x;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&x);
		printf("%lld\n",phi(x));
	}
	return 0;
}

考虑如何用类似于筛素数的方法筛出欧拉函数。
类似于素数,可以做出如下分类讨论:(设p为素数)
P h i ( p ) = p − 1 Phi(p)=p-1 Phi(p)=p1
若 已 知 P h i ( x ) , p r j 能 整 除 x : P h i ( x ∗ ) = P h i ( x ) ∗ p 若已知Phi(x),pr_j能整除x: Phi(x*)=Phi(x)*p Phi(x),prjx:Phi(x)=Phi(x)p
若 已 知 P h i ( x ) , 且 p 不 能 整 除 x : P h i ( x ∗ p r j ) = P h i ( x ) ∗ ( p r j − 1 ) 若已知Phi(x),且p不能整除x:Phi( x* pr_j )=Phi(x)*(pr_j-1) Phi(x),pxPhi(xprj)=Phi(x)(prj1)
简单地证明上述式子:对于①,由于 p p p本身是一个素数,那么比它小的所有数都和它互质,因此答案为 p − 1 p-1 p1。对于②, p p p已经是x的质因子,因此我们看看欧拉函数的定义式可以的出括号相乘部分不会改变,只会把最前面的 x x x变成 x ∗ p x*p xp。对于③ p p p是新加入的质因子,相当于式子前面多乘了个p,然后多加了一个括号,所以相当于原式多乘了: p ∗ ( 1 − 1 / p ) 即 ( p − 1 ) p*(1-1/p)即(p-1) p(11/p)(p1)

 #include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
#define N 1000100
using namespace std;

LL phi[N],n,pr[N],tot = 0,ans = 0;
bool pd[N];
void getphi()
{
	phi[1] = 1;
	for(int i=2;i<=n;i++)
	{
		if( pd[i]==false )
		{
			pr[++tot] = i;
			phi[ i ] = i-1;
		}
		for(int j=1;j<=tot && pr[j]*i<=n;j++)
		{
			LL t = i*pr[j];
			pd[ t ] = true;
			if( i%pr[j] == 0 )
			{
				phi[ t ] = phi[i] * pr[j];
				break;
			}
			else phi[ t ] = phi[i] * (pr[j] - 1);
		}
	}
}
int main()
{
	memset(pd,false,sizeof(pd));
	cin>>n;
	getphi();
	for(int i=1;i<=n;i++)
	{
		ans += phi[i];
	}
	cout<<ans;
	return 0;
}

例题
在这里插入图片描述

数论分块

在介绍整除分块之前,我们先来看一道算数题:已知正整数n,求
∑ i = 1 n ⌊ n i ⌋ \sum_{i=1}^n \lfloor \frac{n}{i} \rfloor i=1nin
我们写一个表格看一看1-20的整除是什么样子的
在这里插入图片描述
表中同样的值会连续出现,而相同的值所划分的区间积是整出分块。整除的性质使得从1到n的数组表可根据数值划分为不同的分块,且分块数远远小于n。利用这种性质,我们如果能推导出每个分块具体的左右端点位置在哪,这个问题就可以快速求解出来了。

推导公式:

搬出例题: ∑ i = 1 n ⌊ n i ⌋ \sum_{i=1}^n \lfloor \frac{n}{i} \rfloor i=1nin

假设我们已知某一个分块的左端点 l l l,要求解出该分块的右端点 r r r。设该分块的数值为 k k k,对于该分块中的每个数 i i i,有 k = ⌊ n l ⌋ = ⌊ n i ⌋ k = \lfloor \frac{n}{l} \rfloor = \lfloor \frac{n}{i} \rfloor k=ln=in i ∗ k ≤ n i*k \le n ikn,也就是说我们找到可得使 i ∗ k ≤ n i*k \le n ikn成立的最大 i i i的值即是我们所求的右端点 r r r,因此我们可以得到下列式子:
k = ⌊ n l ⌋ k = \lfloor \frac{n}{l} \rfloor k=ln

r = m a x ( i ) , i ∗ k ≤ n r = max(i) , i*k \le n r=max(i),ikn

推导可得:
r = ⌊ n k ⌋ = ⌊ n ⌊ n l ⌋ ⌋ r = \lfloor \frac{n}{k} \rfloor = \lfloor \frac{n}{ \lfloor \frac{n}{l} \rfloor } \rfloor r=kn=lnn

转换成代码就是:

ans = 0;
for(int l = 1, r; l <= n; l = r + 1)
{
    r = n / (n / l);
    ans += n / l * (r - l + 1);
}

例题:
求 ∑ i = 1 n ⌊ n a x + b ⌋ 求 \sum_{i=1}^n \lfloor \frac{n}{ax+b} \rfloor i=1nax+bn

简单组合计数

1. a , b a,b a,b很小

#include<bits/stdc++.h>
#define LL long long
#define N 2010
using namespace std;
const LL MOD = 1e9+7;
LL c[N][N],n,a,b;
void init()
{
	for(int i=0;i<=2000;i++)
	{
		for(int j=0;j<=i;j++)
		{
			if( !j ) c[i][j] = 1;
			else c[i][j] = (c[i-1][j] + c[i-1][j-1] )%MOD;
		}
	}	
} 

int main()
{
	cin>>n;
	init();
	while( n-- )
	{
		scanf("%lld %lld",&a,&b);
		printf("%lld\n",c[a][b]);
	}
	return 0;
}

2. a , b ≤ 10000 a,b \leq10000 a,b10000 ,求组合数模p之后的值
在这里插入图片描述

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define N 101001
#define LL long long
using namespace std;
const LL MOD = 1e9+7;
LL quick(LL a,LL b,LL p)
{
	LL ans = 1;
	while( b )
	{
		if( b&1 ) ans = ans * a % p;
		a = a*a % p;
		b>>=1;
	}
	return ans;
}

LL n,fact[N],infact[N],a,b;

void init()
{
	fact[0] = infact[0] = 1;
	for(int i=1;i<N;i++)
	{
		fact[i] = ( fact[i-1] * i ) % MOD;
		infact[i] = ( infact[i-1] * quick(i,MOD-2,MOD) ) % MOD;
	}
}



int main()
{
	cin>>n;
	init();
	while( n-- )
	{
		scanf("%lld %lld",&a,&b);
		printf("%lld\n", fact[a] * infact[b] % MOD * infact[a-b] % MOD );
	}
	return 0;
}

3. a , b ≤ 1 0 18 a,b \leq 10^{18} a,b1018求组合数模p的值

卢卡斯定理
在这里插入图片描述

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
using namespace std;


LL quick(LL a,LL b,LL p)
{
	LL res = 1;
	while( b )
	{
		if( b&1 ) res = res * a % p;
		a = a * a % p;
		b>>=1;
	}
	return res;
}

LL C(LL a,LL b,LL p)
{
	if( b>a ) return 0;
	LL ans = 1;
	for(int i=1,j=a;i<=b;i++,j--)
	{
		ans = ans * j % p;
		ans = ans * quick( i,p-2,p ) % p;
	}
	return ans;
}

LL lucas(LL a,LL b,LL p)
{
	if( a<p && b<p ) return C(a,b,p);
	return C( a%p,b%p,p ) * lucas( a/p,b/p,p )%p;
}

int main()
{
    LL n,a,b,p;
	cin>>n;
	while( n-- )
	{
		scanf("%lld %lld %lld",&a,&b,&p);
		printf("%lld\n",lucas(a,b,p));
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值