目录
《1》数据范围比较小时,(求一个数的欧拉函数时)可以暴力求解
《1》给定 n 对正整数 ai,bi,对于每对数,求出一组 xi,yi,使其满足 a×x+b×y=gcd(a,b)
也可以用以下代码,求出x,y的同时,也可以顺便求出最大公约数,即gcd(a,b)
《2》给定 n 对正整数 ai,bi,对于每对数,求出一组 xi,yi,使其满足 a×x+b×y=c
《2》在计算的过程中预处理出阶乘与逆元并用数组记录下来,时间复杂度:nlogn
《3》预处理用lucas定理来算 , 时间复杂度:n * log(p)N * p * log p)
有向图游戏的和的SG函数值等于它包含的各个⼦游戏SG函数值的异或和,即:
SG(G) = SG(G1) ^ SG(G2) ^ … ^ SG(Gm)
一.质数
《1》试除法判断质数
试除法判定质数(复杂度sqrt(n))
从2到sqrt(n)遍历
bool is_prime(int m){
if(m<2) return false;
for(int i=2;i<=m/i;i++){
if(m%i==0) return false;
}
return true;
}
《2》分解质因数:
(需要注意还要判断质因数>x/i的情况)
void prime_put(int x){
for(int i=2;i<=x/i;i++){
if(x%i==0){
int s=0;
while(x%i==0){
x/=i;
s++;
}
cout<<i<<' '<<s<<endl;
}
}
if(x>1) cout<<x<<' '<<1<<endl;
cout<<endl;
}
《3》筛质数:
每次都找最小的质因数
const int N=1e6+10;
int prime[N];
bool st[N];
int cnt=0;
void get_prime(int n){
for(int i=2;i<=n;i++){
//如果没有标记就说明它为质数,则记录下来
if(!st[i]) prime[cnt++]=i;
//从质数表里面找i的最小质因数,找到就跳出,防止重复筛
//没找到质因数之前就把这个i的质数倍筛走
for(int j=0;prime[j]<=n/i;j++){
st[prime[j]*i]=true;
if(i%prime[j]==0) break;
}
}
cout<<cnt;
}
二.约数
《1》试除法求约数
约数包括1,约数在x/i以下时都是成对出现的,不要忘记特判i=x/i的情况
#include<bits/stdc++.h>
using namespace std;
#define ll long long
vector<ll> get_yueshu(ll x){
vector<ll> res;
for(int i=1;i<=x/i;i++){
if(x%i==0)
{
res.push_back(i);
if(i!=x/i) res.push_back(x/i);
}
}
sort(res.begin(),res.end());
return res;
}
int main(){
ll n;
cin>>n;
while(n--){
ll a;
cin>>a;
auto res=get_yueshu(a);
//学会这种用vector输出的方式
for(auto x:res) cout<<x<<' ';
cout<<endl;
}
}
《2》求约数个数
N=p1c1∗p2c2∗…∗pkck
约数个数:(c1+1)∗(c2+1)∗…∗(ck+1)
约数个数就等于每个质因子的次数+1之后分别相乘
用 unordered_map来存质数与个数
最后结果取模运算时,需要用res=res···,不能用res*=res...
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=210;
const int mod=1e9+7;
int main(){
int n;
cin>>n;
unordered_map<int,int>primes;
while(n--){
int x;
cin>>x;
for(int i=2;i<=x/i;i++)
while(x%i==0)
{
x/=i;
primes[i]++;
}
if(x>1) primes[x]++;
}
ll res=1;
for(auto p:primes) res=res*(p.second%mod+1)%mod;
cout<<res;
}
《3》约数之和
N=p1c1∗p2c2∗…∗pkck
约数之和: (p10+p11+…+p1c1)∗…∗(pk0+pk1+…+pkck)
约数之和等于每个质因子从0次幂一直加到c1次幂(质因子的次幂)之和再分别相乘
需要注意一下从0次幂一直加到c1次幂所用代码的小技巧:
while (b -- ) t = (t * a + 1) % mod;
t=t∗p+1
t=1
t=p+1
t=p2+p+1
…
t=pb+pb−1+…+1
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=210;
const int mod=1e9+7;
int main(){
int n;
cin>>n;
unordered_map<int,int>primes;
while(n--){
int x;
cin>>x;
for(int i=2;i<=x/i;i++)
while(x%i==0)
{
x/=i;
primes[i]++;
}
if(x>1) primes[x]++;
}
ll res=1;
for(auto p:primes){
ll a=p.first,b=p.second;
ll t=1;
while(b--) t=(t*a+1)%mod;
res=res*t%mod;
}
cout<<res;
}
《4》最大公约数
用辗转相除法的原理,通过递归来实现
int gcd(int a,int b){
return b?gcd(b,a%b):a;
}
三.欧拉函数
欧拉函数的定义
《1》数据范围比较小时,(求一个数的欧拉函数时)可以暴力求解
暴力代码如下(复杂度时n根号n):
int eular(int x){
int res=x;
for(int i=2;i<=x/i;i++){
if(x%i==0){
res=res/i*(i-1);
while(x%i==0) x/=i;
}
}
if(x>1) res=res/x*(x-1);
return res;
}
《2》筛法求欧拉函数(求素数的同时求每个数的欧拉函数):
数据范围较大或者求1--n的每个数的欧拉函数的时候
代码中需要注意的点有三个:
【1】当i是质数的时候,phi[i]=i-1;
【2】当i%prime[j]==0的时候,这时候prime[j]是i的最小质因子,因此它也是i*prime[j]的最小质因子,但是由于计算phi[i]的时候已经将1-1/prime[j]乘上了,因此phi[prime[j]*i]只需要将N再×prime[j]即可,因此此时phi[prime[j]*i]=phi[i]*prime[j];
【3】当i%prime[j]!=0的时候,由于prime[j]不是i的质因子,因此计算i*prime[j]的欧拉函数的时候只需要乘上prime[j]*(1-1/prime[j]),即直接乘上(prime[j]-1)即可,因此此时phi[i*prime[j]]=
phi[i]*(prime[j]-1);
代码如下:
void eular(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]=true;
if(i%prime[j]==0){
phi[prime[j]*i]=phi[i]*prime[j];
break;
}
else phi[prime[j]*i]=phi[i]*(prime[j]-1);
}
}
}
四.快速幂
《1》计算快速幂
快速幂利用了二进制从而优化了复杂度
例如计算a的b次方的时候,将b转化成二进制,然后转化成n位底数相同,质数为2的多少次方的数相乘,从而化简了步骤;
代码如下:
ll qmi(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;
}
《2》快速幂求逆元
当a与mod互质的时候,a在模mod的条件下a的逆元就等于a的mod-2次方模mod
代码如下:
a的逆元即为:
ll qmi(int a,int b,int p){
ll res=1%p;
while(b){
if(b&1) res=res*a%p;
a=a*(ll)a%p;
b>>=1;
}
return res;
}
//逆元为qmi(a,p-2,p)
五.扩展欧几里得算法
《1》给定 n 对正整数 ai,bi,对于每对数,求出一组 xi,yi,使其满足 a×x+b×y=gcd(a,b)
(1)当b==0的时候,gcd(a,b)==a,所以这时候就可以取x==1,y==0;
(2)当b不等于0的时候,就利用gcd(a,b)==gcd(b,a%b)来计算,
首先由gcd(b,a%b)可知gcd(b,a%b)==b*x2+(a-[a/b]*b)*y2
而gcd(a,b)==a*x1+b*y1,为了与这个式子对应,可以将第一个式子换算成
a*y2+b*(x2-[a/b]*y2),将x2,y2互换,就变成a*y2+b*(y2-[a/b]*x2),让这个式子与第二个式子对应相等,就可以变成x1=y2,y1=y2-[a/b]*x2
代码如下:
void exgcd(int a,int b,int &x,int &y){
if(!b) x=1,y=0;
else exgcd(b,a%b,y,x),y-=a/b*x;
}
也可以用以下代码,求出x,y的同时,也可以顺便求出最大公约数,即gcd(a,b)
int exgcd(int a,int b,int &x,int &y){
if(!b){
x=1,y=0;
return a;
}
int d=exgcd(b,a%b,y,x);
y-=a/b*x;
return d;
}
《2》给定 n 对正整数 ai,bi,对于每对数,求出一组 xi,yi,使其满足 a×x+b×y=c
当c%gcd(a,b)==0的时候,只需要输出x=x*c/(gcd(a,b)),y=y*c/(gcd(a,b))即可
代码如下:
int exgcd(int a,int b,int &x,int &y){
if(!b){
x=1,y=0;
return a;
}
int d=exgcd(b,a%b,y,x);
y-=a/b*x;
return d;
}
cout<<x*c/d<<y*c/d<<endl;
《3》线性同余方程
给定 n 组数据 ai,bi,mi,对于每组数求出一个 xi,使其满足 ai×xi≡bi(modmi)
同余方程可以化简成a*x=-(y)*m+b,即a*x+m*y=b,求出满足方程的x即可,因此用扩展欧几里得解决即可
当b等于1时,所求x即为a的逆元!!!
代码如下:
int exgcd(int a,int b,int &x,int &y){
if(!b){
x=1,y=0;
return a;
}
int d=exgcd(b,a%b,y,x);
y-=a/b*x;
return d;
}
int d=exgcd(a,m,x,y);
if(b%d==0) cout<<(ll)x*(b/d)%m<<endl;
六.中国剩余定理(回头再写)
七.高斯消元(回头再写)
八.求组合数
《1》预处理的时候利用递推式来算,时间复杂度:n^2
递推式:C(a,b)=C(a-1,b)+C(a-1,b-1);
预处理代码如下:
void init(){
for(int i=0;i<N;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;
}
}
}
《2》在计算的过程中预处理出阶乘与逆元并用数组记录下来,时间复杂度:nlogn
代码如下:
int qmi(int a,int b,int p){
int res=1;
while(b){
if(b&1) res=(ll)res*a%p;
a=(ll)a*a%p;
b>>=1;
}
return res;
}
void init(){
fact[0]=1,infact[0]=1;
for(int i=1;i<=N;i++){
fact[i]=(ll)fact[i-1]*i%mod;
infact[i]=(ll)infact[i-1]*qmi(i,mod-2,mod)%mod;
}
}
《3》预处理用lucas定理来算 , 时间复杂度:n * log(p)N * p * log p)
a,b很大,n很小
lucas定理:C(a,b)=C(a%p,b%p)*C(a/p,b/p);
代码如下:
ll qmi(ll a,ll b,ll p){
ll res=1;
while(b){
if(b&1) res=(ll)res*a%p;
a=(ll)a*a%p;
b>>=1;
}
return res;
}
ll c(ll a,ll b){
if(b>a) return 0;
ll res=1;
for(int i=1,j=a;i<=b;i++,j--){
res=(ll)res*j%p;
res=(ll)res*qmi(i,p-2,p)%p;
}
return res;
}
ll lucas(ll a,ll b){
if(a<p&&b<p) return c(a,b)%p;
return (ll)c(a%p,b%p)*lucas(a/p,b/p)%p;
}
《4》计算高精度组合数(先跳过)
《5》卡特兰数
给定 n 个 0 和 n 个 1,它们将按照某种顺序排成长度为 2n 的序列,求它们能排列成的所有序列中,能够满足任意前缀序列中 0 的个数都不少于 1 的个数的序列有多少个。
由下图可以推出答案为:C(2n,n)-C(2n,n-1)
即C(2n,n)/(n+1)
九.容斥原理
容斥原理:链接AcWing 890. 能被整除的数 - AcWing
题目:活动 - AcWing
利用二进制1和0代表选与不选,从而枚举出所有情况,并且选的数量为奇数时+,选的数量为偶数时-
代码如下:
ll daan(ll x){
ll res=0;
for(int i=1;i<1<<m;i++){
ll t=1,s=0; //t代表整数乘积,s代表选中的数量
for(int j=0;j<m;j++){
if((i>>j)&1){
if((ll)t*p[j]>n){
t=-1;
break;
}
t=(ll)t*p[j];
s++;
}
}
if(t!=-1){
if(s&1) res+=x/t;
else res-=x/t;
}
}
return res;
}
十.博弈论
《1》NIM游戏
题目:活动 - AcWing
题解,原理:AcWing 891. Nim游戏 - AcWing
《2》集合Nim游戏
题目:活动 - AcWing
《3》拆分Nim游戏
题目:活动 - AcWing
集合和拆分nim游戏需要注意:一个局面拆分成了两个局面,由SG函数理论,多个独立局面的SG值,等于这些局面SG值的异或和。
需要用unordered<set>来存sg(x)所能到达的局面,然后用mex理论来判断sg(x)的值
《2》博弈论总结,以下来自y总模板整理: