数论基础模板
数学
质数,约数,欧拉函数,快速幂,扩展欧几里得算法,中国剩余定理,高斯消元,求组合数,容斥原理,博弈论等内容。
质数
质数的判定
试除法 O(sqrt(n))
bool is_prime(int x)
{
if(x<2) return 0;
for(int i=2;i<=x/i;i++) //sqrt(n)
if(x%i==0) return 0;
return 1;
}
分解质因数
试除法
对于每个x,最多包含一个大于sqr(n)的质因数,
所以只需要枚举到sqrt(n).
void divide(int x)
{
for(int i=2;i<=x/i;i++)
{
if(x%i==0)
{
int p=0;
while(x%i==0)
{
x/=i;
p++;//记录次数
}
printf("%d %d\n",i,p);
}
}
if(x>1) printf("%d %d\n",x,1);
printf("\n");
}
质数筛
朴素(nlogn)
//筛1~n的所有质数
void get_prime(int n)
{
for(int i=2;i<=n;i++)
{
if(!st[i])
{
cnt++;
for(int j=i+i;j<=n;j+=i) st[j]=1;
}
}
}
埃氏筛O(nloglogn)
只筛质数的倍数
void get_prime(int x)
{
for(int i=2;i<=n;i++)
{
if(!st[i])
{
cnt++;
for(int j=i+i;j<=n;j+=i) st[j]=1;
}
}
}
线性筛
每个合数只会被筛掉一次且只会被最小的质数筛掉。
void get_prime(int n)
{
for(int i=2;i<=n;i++)
{
if(!st[i]) prime[cnt++]=i;
for(int j=0;prime[j]<=n/i;j++)
{
st[prime[j]*i]=1;
if(i%prime[j]==0) break;
}
}
}
约数
试除法求约数
vector<int> get_divisors(int n)
{
vector<int>res;
for(int i=1;i<=n/i;i++)
{
if(n%i==0)
{
res.push_back(i);
if(i!=n/i) res.push_back(n/i);
}
}
sort(res.begin(),res.end());
return res;
}
约数的个数 O(logn)
x=p1a1+p2a2....pk^ak
num=(a1+1)*(a2+1).....(ak+1)
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod=1e9+7;
int main(){
unordered_map<int,int>prime;
int n;
cin>>n;
while(n--)
{
int x;
cin>>x;
for (int i=2; i<=x/i;i++)
{
while (x%i==0)
{
x/=i;
prime[i]++;
}
}
if (x>1)prime[x]++;
}
LL res=1;
for(auto pr:prime) res=res*(pr.second+1)%mod;
printf("%d",res);
return 0;
}
约数之和
sum=(p10+p21+...p1a1)*(p20+...p2a2)*....(pk0+...pk^ak)
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod=1e9+7;
int main(){
unordered_map<int,int>prime;
int n;
cin>>n;
while(n--)
{
int x;
cin>>x;
for (int i=2; i<=x/i;i++)
{
while (x%i==0)
{
x/=i;
prime[i]++;
}
}
if (x>1)prime[x]++;
}
LL res=1;
for(auto prime:prime)
{
LL t=1;
int p=prime.first,a=prime.second;
while(a--) t=(t*p+1)%mod;
res=res*t%mod;
}
printf("%lld",res);
return 0;
}
最大公约数
欧几里得定理:c|a,c|b--->c|ax+by
int gcd(int a,int b){return b?return(b,a%b):a;}
欧拉函数
φ(n) :1~n中与n互质的数的个数
n=p1a1*p2a2*p3a3......pkak;
φ(n)=n(1-1/p1)(1-1/p2)*...(1-1/pk);
容斥原理
1.从1~n中去掉p1,p2,p3...pk的所有倍数。
2.加上所有p1p2,p1p3...的倍数
3.减去所有p1p2p3...的倍数
...
即得上式。
筛法求欧拉函数
写法1:
1.如果i为质数那么显然phi[i]=i-1;
2.if i%prime[j]==0 prime[j]是i的最小质因数,那么其已经包含在phi[i]中了,那么phi[prime[j]*i]=phi[i]*prime[j];
3.i%prime[j]!=0 phi[prime[j]*i]=prime[j]*(1-1/prime[j])*phi[i]=phi[j]*(prime[j]+1);
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
typedef long long LL;
LL prime[N],cnt,phi[N];
bool st[N];
LL get_euler(int n)
{
phi[1]=1;
for(int i=2;i<=n;i++)
{
if(!st[i])
{
prime[cnt++]=i;
phi[i]=i-1;
}
for(int j=0;prime[j]<=n/i;j++)
{
st[prime[j]*i]=1;
if(i%prime[j]==0)
{
phi[prime[j]*i]=phi[i]*prime[j];
break;
}
else phi[prime[j]*i]=phi[i]*(prime[j]-1);
}
}
LL res=0;
for(int i=1;i<=n;i++) res+=phi[i];
return res;
}
int main(){
int n;
cin>>n;
printf("%lld\n",get_euler(n));
return 0;
}
写法2:
void get(int n)
{
for(int i=2;i<=n;i++) phi[i]=i;
for(int i=2;i<=n;i++)
{
if(phi[i]!=i) continue;
for(int j=i;j<=n;j+=i)
phi[j]=phi[j]/i*(i-1);
}
}
欧拉定理:若a与n互质则a^φ(n)≡1(mod n)
费马定理:若a与p互质且p为质数则a^p-1≡(mod p)
快速幂(反复平方法)
求 a^k mod p
O(log k)
int qm(int a,int b,int p)
{
int res=1;
while(b)
{
if(b&1) res=res*a%p;
b>>=1;
a=a*a%p;
}
return res;
}
快速幂求逆元
乘法逆元:若b,m互质,且对于任意整数a,如果b|a,则存在一个整数,
使得a/b≡a*x(mod m)则称x为b的模m乘法逆元,记为b^-1 (mod m)
b存在逆元的充要条件是b与m互质。
由费马定理得b与m互质则
b^m-1≡1(mod m)
b*b^m-2≡1(mod m)
当模数m为质数是b^m-2即为b的乘法逆元。
扩展欧几里得
裴蜀定理:若a,b为正整数,则存在x,y使得ax+by=gcd(a,b)
扩展欧几里得即已知a,b求出一对x,y满足上式。
int exgcd(int a,int b,int &x,int &y)
{
if(!b)
{
x=1,y=0;
return a;
}
int x1,y1;
int d=exgcd(b,a%b,x1,y1);
x=y1,y=x1-a/b*y1;
return d;
}
证明:
a*x+b*y=gcd(a,b) b=0时 a*x=gcd(a,0)
即x=1,y=0;
gcd(a,b)=gcd(b,a%b);
b*x`+(a%b)y`=gcd(b,a%b)=gcd(a,b)
b*x`+(a-a/b*b)y`=gcd(a,b);
a*y`+b(x`-a/b*y`)=gcd(a,b);
故x=y`,y=x`-a/b*y`;
求一般方程
a*x+b*y=c;
将此式转化为扩展欧几里得
设d=gcd(a,b)
a*x/c*d+b*y/c*d=d;
....
中国剩余定理
中国剩余定理说明:假设整数m1,m2, ... ,mn两两互质,则对任意的整数:a1,a2, ... ,an,方程组 有解。
设M=m1m2m3....*mk;
Mi=M/mi;
因为m两两互质,所以Mi显然与mi互质,Mi^-1为Mi模mi的乘法逆元。
x通解:x=a1M1M1-1+a2M2M2-1+...
令上式模m1,因为M1与m1互质,所以得一,而M2,M3..都包含m1所以得0,因此x=a1(mod m1);
求解一般方程(无互质前提)
等价于
x=k1*m1+a1;
x=k2*m2+a2;
所以
k1* m1+a1=k2*m2+a2;
k1m1+k2m2=a2-a1;①
利用扩展欧几里得求得
k1*m1+k2
*m2=gcd(m1,m2);②
d=gcd(m1,m2);
当且仅当d|(a2-a1)时①有解。
②*(a2-a1)/d=①;
所以k1=k1`*t;
通解:
k1=k1`+k*m2/d;
k2=k2`-k*m1/d;
t=m2/d;
为了得到k1的最小正整数解
(k1%t+t)%t
将k1=k1`+km2/d带入原式x=k1m1+a1;
得到
x=(k1+k*m2/d)*m1+a1; x=k*m1*m2/d + a1+k1
m1;
发现与原式有相似结构
设m0=m1m2/d,a0=a1+k1`m1;
x=k*m0+a0;
得到
m1=m1*m2/d;
a1=a1+k1`*m1;
...
综上x=(x1%m1+m1)%m1;
高斯消元
- 把某一行乘一个非零的数
- 交换某两行
- 把某行的若干倍加到另一行上去。
完美阶梯形---有唯一解
0=非零-------无解
0=0----------无穷多解
枚举每一列c
- 找到这一列绝对值最大的那一行
- 将该行换到最上面
- 将该行第一个数变成一
- 将下面所有行的当前c列变成0.
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 110;
const double eps = 1e-6;
int n;
double a[N][N];
int gauss()
{
int c, r;// c 代表 列 col , r 代表 行 row
for (c = 0, r = 0; c < n; c ++ )
{
int t = r;// 先找到当前这一列,绝对值最大的一个数字所在的行号
for (int i = r; i < n; i ++ )
if (fabs(a[i][c]) > fabs(a[t][c]))
t = i;
if (fabs(a[t][c]) < eps) continue;// 如果当前这一列的最大数都是 0 ,那么所有数都是 0,就没必要去算了,因为它的约束方程,可能在上面几行
for (int i = c; i < n + 1; i ++ ) swap(a[t][i], a[r][i]);//// 把当前这一行,换到最上面(不是第一行,是第 r 行)去
for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c];// 把当前这一行的第一个数,变成 1, 方程两边同时除以 第一个数,必须要到着算,不然第一个数直接变1,系数就被篡改,后面的数字没法算
for (int i = r + 1; i < n; i ++ )// 把当前列下面的所有数,全部消成 0
if (fabs(a[i][c]) > eps)// 如果非0 再操作,已经是 0就没必要操作了
for (int j = n; j >= c; j -- )// 从后往前,当前行的每个数字,都减去对应列 * 行首非0的数字,这样就能保证第一个数字是 a[i][0] -= 1*a[i][0];
a[i][j] -= a[r][j] * a[i][c];
r ++ ;// 这一行的工作做完,换下一行
}
if (r < n)// 说明剩下方程的个数是小于 n 的,说明不是唯一解,判断是无解还是无穷多解
{// 因为已经是阶梯型,所以 r ~ n-1 的值应该都为 0
for (int i = r; i < n; i ++ )//
if (fabs(a[i][n]) > eps)// a[i][n] 代表 b_i ,即 左边=0,右边=b_i,0 != b_i, 所以无解。
return 2;
return 1;// 否则, 0 = 0,就是r ~ n-1的方程都是多余方程
}
// 唯一解 ↓,从下往上回代,得到方程的解
for (int i = n - 1; i >= 0; i -- )
for (int j = i + 1; j < n; j ++ )
a[i][n] -= a[j][n] * a[i][j];//因为只要得到解,所以只用对 b_i 进行操作,中间的值,可以不用操作,因为不用输出
return 0;
}
int main()
{
cin >> n;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n + 1; j ++ )
cin >> a[i][j];
int t = gauss();
if (t == 0)
{
for (int i = 0; i < n; i ++ ) printf("%.2lf\n", a[i][n]);
}
else if (t == 1) puts("Infinite group solutions");
else puts("No solution");
return 0;
}
组合数
递推式
等式左边表示从 m个元素中选取 n 个元素,而等式右边表示这一个过程的另一种实现方法:任意选择m中的某个备选元素为特殊元素,从 m 中选 n 个元素可以由此特殊元素的被包含与否分成两类情况,即 n 个被选择元素包含了特殊元素和 n 个被选择元素不包含该特殊元素。前者相当于从 m-1 个元素中选出 n-1 个元素的组合,即 ;后者相当于从 m-1 个元素中选出 n 个元素的组合,即
。
相关运用: 的二项式定理的系数,即为此数列;任何集合的子集个数也为用为此数列,而得出为
个。
卢卡斯定理
885. 求组合数 I
给定 n 组询问,每组询问给定两个整数 a,b,请你输出 C(a,b)mod(1e9+7) 的值。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一组 a 和 b。
输出格式
共 n 行,每行输出一个询问的解。
数据范围
1≤n≤10000,
1≤b≤a≤2000
我们发现询问比较多,但a,b比较小,所以可以直接n^2递推预处理出所组合数。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod=1e9+7;
const int N=2200;
LL c[N][N];
int main(){
int n;
cin>>n;
c[1][1]=1ll;
for(int i=0;i<=2000;i++)
{
for(int j=0;j<=i;j++)
{
if(!j) c[i][j]=1ll;
else c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}
}
for(int i=1;i<=n;i++)
{
int a,b;
cin>>a>>b;
printf("%lld\n",c[a][b]);
}
return 0;
}
886. 求组合数 II
数据范围
1≤n≤10000,
1≤b≤a≤1e5
a,b很大,我们不能再用上边的递推方法了,那我们考虑公式法,
我们可以先处理出n!,那下面怎么办呢?
费马小定理+快速幂求逆元。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=100010;
const int mod=1e9+7;
int fact[N];
int infact[N];
int qm(int a,int b,int p)
{
int res=1;
while(b)
{
if(b&1) res=(LL)res*a%p;
b>>=1;
a=(LL)a*a%p;
}
return res;
}
int main(){
fact[0]=infact[0]=1;
for(int i=1;i<N;i++)
{
fact[i]=(LL)fact[i-1]*i%mod;
infact[i]=(LL)infact[i-1]*qm(i,mod-2,mod)%mod;
}
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int a,b;
scanf("%d%d",&a,&b);
printf("%lld\n",(LL)fact[a]*infact[b]%mod*infact[a-b]%mod);
}
return 0;
}
887. 求组合数 III
给定 n 组询问,每组询问给定三个整数 a,b,p,其中 p 是质数,请你输出 C(a,b)modp 的值。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一组 a,b,p。
输出格式
共 n 行,每行输出一个询问的解。
数据范围
1≤n≤20,
1≤b≤a≤1e18,
1≤p≤1e5,
p只有1e5并且a,b比p大,那么就可以用卢卡斯定理了。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL qm(LL a,LL b,LL p)
{
LL res=1;
while(b)
{
if(b&1) res=res*a%p;
b>>=1;
a=a*a%p;
}
return res;
}
LL C(LL a,LL b,LL p)
{
if(a<b) return 0;
LL res=1;
for(int i=1,j=a;i<=b;i++,j--)
{
res=res*j%p;
res=res*qm(i,p-2,p);
}
return res;
}
LL lucas(LL a,LL b,LL p)
{
if(a<p&&b<p) return C(a,b,p)%p;
else return C(a%p,b%p,p)%p*lucas(a/p,b/p,p)%p;
}
int main(){
int n;
cin>>n;
while(n--)
{
LL a,b,p;
cin>>a>>b>>p;
printf("%lld\n",lucas(a,b,p));
}
return 0;
}
卡特兰数
....
容斥原理
在计数时,必须注意没有重复,没有遗漏。为了使重叠部分不被重复计算,人们研究出一种新的计数方法,这种方法的基本思想是:先不考虑重叠的情况,把包含于某内容中的所有对象的数目先计算出来,然后再把计数时重复计算的数目排斥出去,使得计算的结果既无遗漏又无重复,这种计数的方法称为容斥原理。
简单来讲,奇加偶减
...
不妨设元素x在k个集合( 1 ≤ k ≤ n 1 \le k \le n 1≤k≤n)中出现过,根据公式则其被计算的次数为:
根据二项式定理,可知:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现