acwing模板整理(第四讲)(数学知识)

目录

一.质数

《1》试除法判断质数

《2》分解质因数:(需要注意还要判断质因数>x/i的情况)

《3》筛质数:

每次都找最小的质因数

二.约数

《1》试除法求约数

《2》求约数个数

《3》约数之和

《4》最大公约数

三.欧拉函数

《1》数据范围比较小时,(求一个数的欧拉函数时)可以暴力求解

《2》筛法求欧拉函数(求素数的同时求每个数的欧拉函数):

四.快速幂

《1》计算快速幂

《2》快速幂求逆元

五.扩展欧几里得算法

《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

《3》线性同余方程

当b等于1时,所求x即为a的逆元!!!

六.中国剩余定理(回头再写)

七.高斯消元(回头再写)

八.求组合数

《1》预处理的时候利用递推式来算,时间复杂度:n^2

《2》在计算的过程中预处理出阶乘与逆元并用数组记录下来,时间复杂度:nlogn

《3》预处理用lucas定理来算 ,  时间复杂度:n * log(p)N * p * log p)

《4》计算高精度组合数(先跳过)

《5》卡特兰数

 九.容斥原理

十.博弈论

《1》NIM游戏

《2》集合Nim游戏

《3》拆分Nim游戏

有向图游戏的和的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总模板整理:

NIM 游戏 —— 模板题 AcWing 891. Nim 游戏
给定 N 堆物品,第 i 堆物品有 Ai 个。两名玩家轮流⾏动,每次可以任选⼀堆,取⾛任意多个物品,可把⼀堆取光,
但不能不取。取⾛最后⼀件物品者获胜。两⼈都采取最优策略,问先⼿是否必胜。
我们把这种游戏称为 NIM 博弈。把游戏过程中⾯临的状态称为局⾯。整局游戏第⼀个⾏动的称为先⼿,第⼆个⾏
动的称为后⼿。若在某⼀局⾯下⽆论采取何种⾏动,都会输掉游戏,则称该局⾯必败。
所谓采取最优策略是指,若在某⼀局⾯下存在某种⾏动,使得⾏动后对⾯⾯临必败局⾯,则优先采取该⾏动。同
时,这样的局⾯被称为必胜。我们讨论的博弈问题⼀般都只考虑理想情况,即两⼈均⽆失误,都采取最优策略⾏
动时游戏的结果。
NIM 博弈不存在平局,只有先⼿必胜和先⼿必败两种情况。
定理: NIM 博弈先⼿必胜,当且仅当 A1 ^ A2 ^ … ^ An != 0
公平组合游戏 ICG
若⼀个游戏满⾜:
1. 由两名玩家交替⾏动;
2. 在游戏进程的任意时刻,可以执⾏的合法⾏动与轮到哪名玩家⽆关;
3. 不能⾏动的玩家判负;
则称该游戏为⼀个公平组合游戏。
NIM 博弈属于公平组合游戏,但城建的棋类游戏,⽐如围棋,就不是公平组合游戏。因为围棋交战双⽅分别只能 落⿊⼦和⽩⼦,胜负判定也⽐较复杂,不满⾜条件 2 和条件 3
有向图游戏
给定⼀个有向⽆环图,图中有⼀个唯⼀的起点,在起点上放有⼀枚棋⼦。两名玩家交替地把这枚棋⼦沿有向边进⾏移动,每次可以移动⼀步,⽆法移动者判负。该游戏被称为有向图游戏。
任何⼀个公平组合游戏都可以转化为有向图游戏。具体⽅法是,把每个局⾯看成图中的⼀个节点,并且从每个局 ⾯向沿着合法⾏动能够到达的下⼀个局⾯连有向边。
Mex 运算
S 表⽰⼀个⾮负整数集合。定义 mex(S) 为求出不属于集合 S 的最⼩⾮负整数的运算,即:
mex(S) = min{x}, x 属于⾃然数,且 x 不属于 S
SG 函数 模板
在有向图游戏中,对于每个节点 x ,设从 x 出发共有 k 条有向边,分别到达节点 y1, y2, …, yk ,定义 SG(x) x 的后继
节点 y1, y2, …, yk SG 函数值构成的集合再执⾏ mex(S) 运算的结果,即:
SG(x) = mex({SG(y1), SG(y2), …, SG(yk)})
特别地,整个有向图游戏 G SG 函数值被定义为有向图游戏起点 s SG 函数值,即 SG(G) = SG(s)
有向图游戏的和 —— 模板题 AcWing 893. 集合 -Nim 游戏
G1, G2, …, Gm m 个有向图游戏。定义有向图游戏 G ,它的⾏动规则是任选某个有向图游戏 Gi ,并在 Gi 上⾏
动⼀步。 G 被称为有向图游戏 G1, G2, …, Gm 的和。

有向图游戏的和的SG函数值等于它包含的各个⼦游戏SG函数值的异或和,即:

SG(G) = SG(G1) ^ SG(G2) ^ … ^ SG(Gm)

定理
有向图游戏的某个局⾯必胜,当且仅当该局⾯对应节点的 SG 函数值⼤于 0
有向图游戏的某个局⾯必败,当且仅当该局⾯对应节点的 SG 函数值等于 0
  • 27
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值