本周总结(8.3-8.16)(最近公共祖先、数学知识)


本周主要复习数论,包括质数,约数,欧拉函数,快速幂,扩展欧几里得算法,中国剩余定理,高斯消元,求组合数,容斥原理等内容。
以及上周的最近公共祖先算法。

算法总结

最近公共祖先(倍增法)

d数组是深度数组,根节点所在的深度为1。
fa[i][j]表示节点i往上跳 2j 次方个节点。
T是最多向上跳的次数,T = (int)(log(n) / (log(2)) + 1,n是节点总数

void bfs() //利用bfs初始化d数组和fa数组
{
    queue<int> q;
    memset(d, 0x3f, sizeof d);
    q.push(root);
    d[root] = 1;
    d[0] = 0; //跳的时候超过了根则记为0(哨兵)
    
    while (q.size())
    {
        int t = q.front();
        q.pop();
        for (int i = 0; i < g[t].size(); i ++ )
        {
            int j = g[t][i];
            if (d[j] > d[t] + 1)
            {
                q.push(j);
                d[j] = d[t] + 1;
                fa[j][0] = t;
                for (int k = 1; k <= T; k ++ ) //从小到大枚举
                {
                    fa[j][k] = fa[fa[j][k - 1]][k - 1]; //倍增思想
                }
            }
        }
    }
}

int lca(int x, int y) //返回x和y的最近公共祖先
{
    if (d[x] < d[y]) swap(x, y);
    
    for (int k = T; k >= 0; k -- ) //从大到小枚举
    {
        if (d[fa[x][k]] >= d[y]) x = fa[x][k];
    }
    
    if (x == y) return x;
    
    for (int k = T; k >= 0; k -- )
    {
        if (fa[x][k] != fa[y][k])
        {
            x = fa[x][k];
            y = fa[y][k];
        }
    }
    
    return fa[x][0];
}

筛质数

线性筛法,每个数只会被标记一次,时间复杂度O(n)

int cnt, p[N]; //cnt是质数的个数,p数组用来存储质数
bool st[N];

void get_p()
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) p[cnt ++ ] = i;
        for (int j = 0; p[j] <= n / i; j ++ )
        {
            st[p[j] * i] = true;
            if (i % p[j] == 0) break;
        }
    }
}

分解质因数

x为待分解的数,分别输出每个质因数的底数和指数。

for (int i = 2; i <= x / i; i ++ )
{
    int s = 0;
    if (x % i == 0)
    {
        while (x % i == 0)
        {
            s ++ ;
            x /= i;
        }
        cout << i << ' ' << s << endl;
    }
}

if (x > 1) cout << x << " 1" << endl;

约数

正整数N = p1α1 * p2α2 * p3α3 * …… * pkαk
(p1、p2、……、pk为质数,此表达式为分解质因数)

约数个数

约数个数 = (α1 + 1) * (α2 + 1) * …… * (αk + 1)

约数之和

约数之和 = (p10 + p11 + …… + p1α1 ) + (p20 + p21 + …… + p2α2 ) + …… + (pk0 + pk1 + …… + pkαk )
类似选择问题,多项式展开后即为所有约数。

最大公约数

返回a和b的最大公约数。

int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}

欧拉函数

在这里插入图片描述
1的欧拉函数为1

分解质因数求欧拉函数

int oula(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;
}

筛法求欧拉函数

求1 ~ n的欧拉函数,存储在phi数组中。

long long oula(int n)
{
    phi[1] = 1; //初始化1的欧拉函数
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i])
        {
            p[cnt ++ ] = i;
            phi[i] = i - 1;
        }
        for (int j = 0; p[j] * i <= n; j ++ )
        {
            st[i * p[j]] = true;
            if (i % p[j] == 0)
            {
                phi[i * p[j]] = phi[i] * p[j];
                break;
            }
            phi[i * p[j]] = phi[i] * (p[j] - 1);
        }
    }
    
    long long res = 0;
    for (int i = 1; i <= n; i ++ ) res += phi[i];
    return res;
}

快速幂

计算 ab % mod

ll qmi(ll a, ll b, ll mod)
{
    ll res = 1;
    while (b)
    {
        if (b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}

扩展欧几里得算法

推算过程:
ax + by = d
by + (a % b)x = d (交换x与y的位置)
ax + b(y - a / b * x) = d (交换后y = y - a / b * x)

int exgcd(int a, int b, int &x, int &y)
{
    if (b == 0)
    {
        x = 1, y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

通解:x = x0 + kb
           y = y0 + ka

中国剩余定理

给定 2n 个整数a1,a2,…,an和m1,m2,…,mn,求一个最小的非负整数 x,满足∀i∈[1,n],x≡mi(mod ai)。

ll a1, m1;
cin >> a1 >> m1;

bool f = true;
for (int i = 0; i < n - 1; i ++ )
{
//每次合并两个式子
    ll a2, m2;
    cin >> a2 >> m2;
    
    ll k1, k2;
    
    ll d = exgcd(a1, a2, k1, k2); //扩展欧几里得
    
    if ((m1 - m2) % d) //不能整除说明无解
    {
        f = false;
        break;
    }
    
    k1 *= (m2 - m1) / d; //同时扩大
    int t = a2 / d; //通解表示
    k1 = (k1 % t + t) % t;
    
    m1 = m1 + a1 * k1; //变换
    a1 = a1 * a2 / d;
}

x = (m1 % a1 + a1) % a1;

高斯消元

int gaoss()
{
    int r, c; //行和列
    for (c = 0, r = 0; c < n; c ++ )
    {
        int t = r; //找出每一列最大的
        for (int i = r; i < n; i ++ )
            if (a[i][c] > a[t][c]) t = i;
            
        if (fabs(a[t][c]) < 1e-6) continue; //最大的为0 则跳过
            
        for (int i = c; i < n + 1; i ++ ) swap(a[t][i], a[r][i]); //将第t行和第r行交换
        
        for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c]; //将a[r][c]变为1
        
        for (int i = r + 1; i < n; i ++ ) //将此列的其他值变为0
            if (fabs(a[i][c]) > 1e-6)
                for (int j = n; j >= c; j -- )
                    a[i][j] -= a[r][j] * a[i][c];
        r ++ ;
    }
    
    if (r < n)
    {
        for (int i = r; i < n; i ++ ) //注意i开始枚举的位置
            if (fabs(a[i][n]) > 1e-6) return 2; //无解
        return 1; //无穷多组解
    }
    for (int i = n - 1; i >= 0; i -- ) //将上三角化为0
    {
        for (int j = i + 1; j < n; j ++ )
            a[i][n] -= a[j][n] * a[i][j];
    }
    return 0; //有解
}

组合数

预处理组合数

利用组合数公式:C(n, m) = C(n - 1, m) + C(n - 1, m - 1),预处理出一定范围内的所有组合数,适用于查询次数较多的情况。

void init()
{
    for (int i = 0; i < N; i ++ )
        for (int j = 0; j <= i; j ++ )
            if (j == 0) c[i][j] = 1;
            else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
}

预处理逆元

首先预处理出所有阶乘取模的余数fact[N],以及所有阶乘取模的逆元infact[N]
如果取模的数是质数,可以用费马小定理求逆元

int qmi(int a, int k, int p)    // 快速幂模板
{
    int res = 1;
    while (k)
    {
        if (k & 1) res = (LL)res * a % p;
        a = (LL)a * a % p;
        k >>= 1;
    }
    return res;
}

// 预处理阶乘的余数和阶乘逆元的余数
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] * qmi(i, mod - 2, mod) % mod;
}

while (n -- )
{
    int a, b;
    scanf("%d%d", &a, &b);
    cout << (LL)fact[a] * infact[b] % mod * infact[a - b] % mod << endl;
}

卢卡斯定理

求C(a, b) mod p,此时01 <= a, b <= 1018,1 <= p <= 105
在这里插入图片描述

int qmi(int a, int b) //快速幂求逆元
{
    int res = 1;
    while (b)
    {
        if (b & 1) res = (ll)res * a % p;
        a = (ll)a * a % p;
        b >>= 1;
    }
    return res;
}

int c(int a, int b) //用公式求组合数
{
    int 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; //乘法逆元
    }
    return res;
}

int l(ll a, ll b) //卢卡斯定理
{
    if (a < p && b < p) return c(a, b);
    return c(a % p, b % p) * (ll)l(a / p, b / p) % p;
}

容斥原理

先不考虑重叠的情况,把包含于某内容中的所有对象的数目先计算出来,然后再把计数时重复计算的数目排斥出去,使得计算的结果既无遗漏又无重复。

其他总结

  1. 快速幂在中间计算时可能要转换成long long类型。
  2. 费马小定理: 如果p是一个质数,而整数a不是p的倍数,则有a(p-1)≡1(mod p)。
  3. 裴蜀定理:对于任意正整数a,b,一定存在非零整数x,y,使得ax+by = (a, b)(a和b的最大公约数)。
  4. 求逆元:b存在乘法逆元的充要条件是b与模数m互质,当模数m为质数时,bm−2即为b的乘法逆元(利用费马小定理求逆元);当模数m不为质数时,用扩展欧几里得算法求逆元。
  5. 判断double类型是否等于0要用fabs(n) < 1e-6来判断(小于较小的数),还要注意括号的位置。

本周拓展题

以下拓展题均来源于acwing网站
质数:1292. 哥德巴赫猜想
约数:198. 反素数
欧拉函数:201. 可见的点
快速幂:1290. 越狱
扩展欧几里得算法:203. 同余方程
中国剩余定理:1300. 生物节律
高斯消元:207. 球形空间产生器
组合数:1311. 组合
容斥原理:1310. 数三角形
博弈论:1319. 移棋子游戏

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值