【蓝桥杯】蓝桥杯算法复习(四)

news2024/11/17 14:44:24

😀大家好,我是白晨,一个不是很能熬夜😫,但是也想日更的人✈。如果喜欢这篇文章,点个赞👍,关注一下👀白晨吧!你的支持就是我最大的动力!💪💪💪

在这里插入图片描述

文章目录

  • 前言
  • 蓝桥杯复习(四)
    • 一、区间DP
      • 复习
      • 练习:游戏
    • 二、树形DP
      • 复习
      • 练习:病毒溯源
    • 三、快速幂
      • 复习
      • 练习:转圈游戏
    • 四、最大公约数
      • 复习
      • 练习:公约数
    • 五、分解质因数
      • 复习
      • 练习:约数的个数
    • 六、矩阵乘法
      • 复习
      • 练习:斐波那契
    • 七、组合计数
      • 复习
      • 练习:计算系数
  • 总结

前言


本文适合有一定算法基础,但是由于各种原因已经很久没有敲代码的同学。本文以复习+练习为主,旨在通过练习算法题快速复习已经遗忘的算法。即使不是参加蓝桥杯的同学,如果符合上面的条件,依然可以参考本文进行复习。

如果你是新手,可以参考白晨的算法专栏进行学习。

如果你想系统性进行复习,可关注白晨的蓝桥杯专栏进行学习。


蓝桥杯复习(四)


一、区间DP


复习

区间动态规划(Interval Dynamic Programming,简称区间DP)是一种动态规划算法,通常用于解决涉及区间范围的优化问题。这类问题通常涉及对给定的区间进行操作,以达到某种特定的目标,例如最大化区间内的价值、最小化区间内的代价等。

区间DP的基本思想是将原始区间划分为更小的子区间,并逐步构建出整个区间的最优解。通常,这种方法需要定义一个状态表示当前处理的区间,然后设计状态转移方程来描述如何从较小的区间状态转移到较大的区间状态。

以下是一个简单的区间DP问题示例:

假设给定一个长度为n的数组a[1…n],每个位置i上有一个值a[i]。现在要选取一个连续的子区间[a, b](1 ≤ a ≤ b ≤ n),使得这个子区间的和最大。

解决这个问题的一个典型的区间DP算法如下:

  1. 定义状态:设dp[i]表示以第i个元素结尾的子区间的最大和。
  2. 初始化:dp[1] = a[1]。
  3. 状态转移方程:dp[i] = max(dp[i-1] + a[i], a[i])。
  4. 最终答案:max(dp[1], dp[2], …, dp[n])。

这里的状态转移方程表示,以第i个元素结尾的子区间的最大和要么是当前元素自成一个区间,要么是以第i-1个元素结尾的子区间的最大和加上当前元素的值。最终的答案就是所有以不同元素结尾的子区间中最大的和。

通过动态规划的方式,可以高效地解决这类区间优化问题,而不需要穷举所有可能的子区间。

  • 区间DP模板
// 先枚举区间长度
for (int len = 1; len <= n; ++len) 
    // 再枚举左端点
    for (int i = 0; i + len - 1 < n; ++i) 
    {
        int j = i + len - 1;
        // 状态转移
    }

练习:游戏

image-20240330102113514

🍬题目链接:游戏

🍎算法思想

  • 状态定义:在拿下标为[l, r]区间中的分时,(当前玩家拿的分 - 下一个玩家拿的分)的最大值

  • 状态划分:由于总分数是一定的,所以一个人分高,另一个人就分少,所以将可以将问题转化为两个玩家分差的最大值,只要求出两个玩家分差最大值,再求出总分数,就能求出两个玩家各自的分。

    所以f(i, j)可以划分为:

    1. 如果取左边的数w[i],下一名玩家就要在[i + 1, j]中取,下一名玩家在[i + 1, j]拿的分的差值最大为f(i + 1, j),所以f(i, j) = max(f(i, j), w[i] - f(i + 1, j));
    2. 同理,如果取左边的数w[j],下一名玩家就要在[i, j - 1]中取,下一名玩家在[i, j - 1]拿的分的差值最大为f(i, j - 1),所以f(i, j) = max(f(i, j), w[i] - f(i, j - 1));
  • 状态转移: f ( i , j ) = m a x ( w [ i ] − f [ i + 1 ] [ r ] , w [ j ] − f [ i ] [ j − 1 ] ) f(i, j) = max(w[i] - f[i + 1][r], w[j] - f[i][j - 1]) f(i,j)=max(w[i]f[i+1][r],w[j]f[i][j1])

🍊具体实现

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110;

int n;

int w[N];
int f[N][N];

int main() 
{
    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d", &w[i]);
    
    for (int len = 1; len <= n; ++len) 
        for (int i = 0; i + len - 1 < n; ++i) 
        {
            int j = i + len - 1;
            f[i][j] = max(w[i] - f[i + 1][j], w[j] - f[i][j - 1]);
        }
        
    int sum = 0, d = f[0][n - 1];
    for (int i = 0; i < n; ++i) sum += w[i];
    
    printf("%d %d", (sum + d) / 2, (sum - d) / 2);
    return 0;
}

二、树形DP


复习

树形动态规划(Tree Dynamic Programming,简称树形DP)是一种应用动态规划思想解决树结构上的问题的方法。在树形DP中,通常需要在树上进行递归或者动态规划的遍历,以解决与节点相关的问题,比如最长路径、最大权值路径等等。

树形DP的基本思想是从树的叶子节点开始,逐步向上计算每个节点的状态,并在计算过程中利用子树的信息来更新父节点的状态。通常,树形DP需要设计适合问题特点的状态表示和状态转移方程。

以下是一个简单的树形DP问题示例:

假设给定一棵树,每个节点有一个权值。现在要找到树上的一条路径,使得路径上节点的权值之和最大。

解决这个问题的一个典型的树形DP算法如下:

  1. 定义状态:设dp[u]表示以节点u为根的子树中的最大路径权值和。
  2. 递归计算:从叶子节点开始向上递归计算每个节点的dp值。
  3. 状态转移方程:对于每个节点u,设它的子节点为v1, v2, …, vk,则dp[u] = max(0, dp[v1] + weight[u], dp[v2] + weight[u], …, dp[vk] + weight[u]),其中weight[u]表示节点u的权值。
  4. 最终答案:树中所有节点的dp值中的最大值即为所求的最大路径权值和。

通过树形DP,可以高效地解决诸如树上最大路径和、树上最长路径等问题,其时间复杂度通常为树的节点数量的线性或者近似线性复杂度。

  • 树形DP模板
const int N = 10010;

int n;
int h[N], e[N], ne[N], idx;
bool has_father[N];

void add(int a, int b) 
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

int main()
{
    memset(h, -1, sizeof(h));
    scanf("%d", &n);
    
    for (int i = 0; i < n; ++i) {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b);
        has_father[b] = true;
    }
    
    int root = 0;
    while (has_father[root]) root++;
    
    // 从根开始进行一些dp操作
    // ...
    return 0;
}

练习:病毒溯源

image-20240330113650916

🍬题目链接:病毒溯源

🍎算法思想

  • 状态定义: 在这个问题中,我们的目标是求解树中的最长路径。因此,我们可以定义状态 dp[u] 表示以节点 u 为根的子树中的最长路径长度。这里 u 是树中的一个节点。

  • 状态划分: 在状态划分中,我们需要考虑如何利用子问题的结果来构建更大规模的问题的解。在这个问题中,我们可以考虑划分每个节点的子树作为不同的子问题。

  • 状态转移 d p [ u ] = m a x ( d p [ s o n 1 ] , d p [ s o n 2 ] , . . . , d p [ s o n k ] ) + 1 dp[u] = max(dp[son1], dp[son2], ..., dp[sonk]) + 1 dp[u]=max(dp[son1],dp[son2],...,dp[sonk])+1,递归计算u节点每一个子树的最大高度,最后选择最高的子树的高度+1就是以u为根的树的最大高度。

🍊具体实现

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 10010;

int n;
int h[N], e[N], ne[N], idx;
int son[N];
bool has_father[N];

void add(int a, int b) 
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

int dfs(int u)
{
    int res = 0;
    
    for (int i = h[u]; ~i; i = ne[i]) {
        int t = e[i];
        int ret = dfs(t);
        if (ret > res) res = ret, son[u] = t;
        else if (ret == res) son[u] = min(son[u], t); 
    }
    
    return res + 1;
}

int main()
{
    memset(h, -1, sizeof(h));
    memset(son, -1, sizeof(son));
    scanf("%d", &n);
    
    for (int i = 0; i < n; ++i) {
        int len;
        scanf("%d", &len);
        
        while (len--) {
            int x;
            scanf("%d", &x);
            add(i, x);
            has_father[x] = true;
        }
    }
    
    int root = 0;
    while (has_father[root]) root++;
    
    printf("%d\n", dfs(root));
    
    printf("%d ", root);
    for (int i = son[root]; ~i; i = son[i]) {
        printf("%d ", i);
    }
    return 0;
}

三、快速幂


复习

快速幂算法(也称为快速幂运算或指数运算)是一种用于快速计算一个数的整数次幂的算法。该算法基于分治策略,通过将幂指数不断折半来减少计算次数,从而提高了计算效率。

下面是快速幂算法的基本思想:

  1. 将幂指数表示为二进制形式。
  2. 从最低位开始,对于每一位:
    • 如果该位为 1,则将结果乘以底数。
    • 将底数平方。
  3. 继续处理下一位,直到处理完所有位。
// a为底数,k为幂数,p为模数
int quick_power(int a, int k, int p)
{
    int res = 1 % p;
    
    while (k) {
        if (k & 1) res = ((long long)res * a) % p;
        a = (long long)a * a % p;
        k >>= 1;
    }
    
    return res;
}

练习:转圈游戏

image-20240402104745696

🍬题目链接:转圈游戏

🍎算法思想

非常基础的快速幂算法的应用,先将轮数用快速幂算法计算出来,再乘以m,就是每个人移动的距离,再加上x的下标,算出x位置最后移动的总距离,最后模上n,就求出了x当前的位置编号。

🍊具体实现

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

int n, m, k, x;


int quick_power(int a, int k, int p)
{
    int res = 1 % p;
    
    while (k) {
        if (k & 1) res = ((long long)res * a) % p;
        a = (long long)a * a % p;
        k >>= 1;
    }
    
    return res;
}


int main()
{
    cin >> n >> m >> k >> x;
    
    printf("%d", (x + quick_power(10, k, n) * (long long)m) % n);
    return 0;
}

四、最大公约数


复习

  • 最大公约数模板
int gcd(int a, int b) 
{
    return b ? gcd(b, a % b): a;
}
  • 试除法求约数
vector<int> get_divisions(int x)
{
    vector<int> res;
    
    for (int i = 1; i <= x / i; ++i)
    {
        if (x % i == 0) 
        {
            res.push_back(i);
            if (x / i != i) res.push_back(x / i);
        }
    }
    return res;
}
  • 二分模板
// 模板一
// 求满足check条件的最左下标
template<class T>
int binary_search1(T* v, int l, int r)
{
	while (l < r)
	{
        int mid = l + r >> 1;
		if (check(v[mid])) // check中 v[mid] 永远放在前面,eg. v[mid] >= a
			r = mid;
		else
			l = mid + 1;
	}

	return mid;
}

// 模板二
// 求满足check条件的最右下标
template<class T>
int binary_search1(T* v, int l, int r)
{
	while (l < r)
	{
        int mid = l + r + 1 >> 1; // 必须加一,避免死循环
		if (check(v[mid])) // eg.v[mid] <= a
			l = mid;
		else
			r = mid - 1;
	}

	return mid;
}

练习:公约数

image-20240403112157493

🍬题目链接:公约数

🍎算法思想

本题考查最大公约数、试除法求约数以及二分算法,很好地将三种基础算法综合了起来,很考察基本功。

这道题要推理一下,本题所要求的x一定是a、b最大公约数cd的约数,可以反证一下,如果x不为cd的约数,那么由算数定理可得,x一定有一个cd没有的质因子或者有一个质因子的幂数比cd的大,由于cda、b的最大公约数,所以cd的质因子一定是a、b中出现的,质因子的幂数一定是a、b相同质因子幂数的最小值。那么,x如果有一个cd没有的质因子或者有一个质因子的幂数比cd的大,说明x不能整除a、b。证毕。

实现分为三步:

  1. 计算最大公约数(gcd):首先,使用辗转相除法计算给定两个整数 ab 的最大公约数。
  2. 获取最大公约数的所有正整数因子:定义一个函数 get_divisions(int x),用于获取给定整数 x 的所有正整数因子。这个函数通过迭代从 1 到 sqrt(x)(平方根)进行检查,如果 x 能够被某个数整除,那么该数就是 x 的一个因子。这些因子被存储在一个向量中,并按升序排序,以便后续的查询步骤更有效。
  3. 处理查询:对于每个查询,程序读取一对整数 LR,表示当前查询的范围。然后,使用二分查找在已经计算好的最大公约数的因子中找到范围 [L, R] 内的最大因子。二分查找是一种高效的查找方法,通过将搜索范围逐步缩小至单个元素来找到目标值或确定目标值不存在。

🍊具体实现

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;

int a, b;
int q;

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

vector<int> get_divisions(int x)
{
    vector<int> res;
    
    for (int i = 1; i <= x / i; ++i)
    {
        if (x % i == 0) 
        {
            res.push_back(i);
            if (x / i != i) res.push_back(x / i);
        }
    }
    // 将约数升序排列
    sort(res.begin(), res.end());
    
    return res;
}

int main()
{
    scanf("%d%d%d", &a, &b, &q);
    int cd = gcd(a, b);
    
    vector<int> v = get_divisions(cd);
    
    while (q--)
    {
        int L, R;
        scanf("%d%d", &L, &R);
        
        int l = 0, r = v.size() - 1;
        while (l < r)
        {
            int mid = l + r + 1 >> 1;
            if (v[mid] <= R) l = mid;
            else r = mid - 1;
        }
        
        if (v[r] <= R && v[r] >= L) printf("%d\n", v[r]);
        else puts("-1");
    }
    return 0;
}

五、分解质因数


复习

  • 质因数公式: x = p 1 n 1 + p 2 n 2 + . . . + p k n k x = p_1^{n_1} + p_2^{n_2} + ... + p_k^{n_k} x=p1n1+p2n2+...+pknk

  • 约数个数: c n t = ( n 1 + 1 ) ∗ ( n 2 + 1 ) ∗ . . . ∗ ( n k + 1 ) cnt = (n_1 + 1)*(n_2+1)*...*(n_k+1) cnt=(n1+1)(n2+1)...(nk+1)

  • 约数之和: s u m = ( p 1 0 + p 1 1 + . . . + p 1 n 1 ) ( p 2 0 + p 2 1 + . . . + p 2 n 2 ) . . . ( p k 0 + . . . + p k n k ) sum = (p_1^0 + p_1^1 + ... + p_1^{n_1})(p_2^0 + p_2^1 + ... + p_2^{n_2})...(p_k^0 + ... + p_k^{n_k}) sum=(p10+p11+...+p1n1)(p20+p21+...+p2n2)...(pk0+...+pknk)

求质因数模板

unordered_map<int, int> m;

void get_divisors(int x)
{
    for (int i = 2; i <= x / i; ++i)
    {
        if (x % i == 0)
        {
            int cnt = 0;
            while (x % i == 0)
            {
                x /= i;
                cnt++;
            }
            m[i] += cnt;
        }
    }

    if (x > 1) m[x] += 1;
}

练习:约数的个数

image-20240403224449380

🍬题目链接:约数的个数

🍎算法思想

  • 质因数公式: x = p 1 n 1 + p 2 n 2 + . . . + p k n k x = p_1^{n_1} + p_2^{n_2} + ... + p_k^{n_k} x=p1n1+p2n2+...+pknk

  • 约数个数: c n t = ( n 1 + 1 ) ∗ ( n 2 + 1 ) ∗ . . . ∗ ( n k + 1 ) cnt = (n_1 + 1)*(n_2+1)*...*(n_k+1) cnt=(n1+1)(n2+1)...(nk+1)

  • 约数之和: s u m = ( p 1 0 + p 1 1 + . . . + p 1 n 1 ) ( p 2 0 + p 2 1 + . . . + p 2 n 2 ) . . . ( p k 0 + . . . + p k n k ) sum = (p_1^0 + p_1^1 + ... + p_1^{n_1})(p_2^0 + p_2^1 + ... + p_2^{n_2})...(p_k^0 + ... + p_k^{n_k}) sum=(p10+p11+...+p1n1)(p20+p21+...+p2n2)...(pk0+...+pknk)

直接分解质因子,然后求约数个数即可。

🍊具体实现

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_map>

using namespace std;

int n;
unordered_map<int, int> um;

void get_divisors(int x)
{
    for (int i = 2; i <= x / i; ++i)
    {
        if (x % i == 0)
        {
            int cnt = 0;
            while (x % i == 0)
            {
                cnt++;
                x /= i;
            }
            um[i] += cnt;
        }
    }
    
    if (x > 1) um[x] += 1;
}

int main()
{
    scanf("%d", &n);
    
    while (n--)
    {
        um.clear();
        int x;
        scanf("%d", &x);
        
        get_divisors(x);
        int res = 1;
        
        for (auto e : um)
        {
            res *= e.second + 1;
        }
        
        printf("%d\n", res);
    }
    return 0;
}

六、矩阵乘法


复习

当我们进行矩阵乘法时,我们需要计算两个矩阵的乘积。假设我们有两个矩阵 A A A B B B,它们的尺寸分别为 m × n m \times n m×n n × p n \times p n×p,结果矩阵 C C C 的尺寸为 m × p m \times p m×p。矩阵乘法的公式如下所示:

C i j = ∑ k = 1 n A i k ⋅ B k j C_{ij} = \sum_{k=1}^{n} A_{ik} \cdot B_{kj} Cij=k=1nAikBkj

其中, C i j C_{ij} Cij 是结果矩阵 C C C 的第 i i i 行第 j j j 列的元素, A i k A_{ik} Aik 是矩阵 A A A 的第 i i i 行第 k k k 列的元素, B k j B_{kj} Bkj 是矩阵 B B B 的第 k k k 行第 j j j 列的元素。

在代码中,我们有两个 2 × 2 2 \times 2 2×2 的矩阵 A A A B B B,它们的乘积 C C C 的计算如下:

C i j = A i 1 ⋅ B 1 j + A i 2 ⋅ B 2 j C_{ij} = A_{i1} \cdot B_{1j} + A_{i2} \cdot B_{2j} Cij=Ai1B1j+Ai2B2j

对于一个 2 × 2 2 \times 2 2×2 的矩阵, C C C 的计算如下所示:

C = ( c 11 c 12 c 21 c 22 ) = ( a 11 ⋅ b 11 + a 12 ⋅ b 21 a 11 ⋅ b 12 + a 12 ⋅ b 22 a 21 ⋅ b 11 + a 22 ⋅ b 21 a 21 ⋅ b 12 + a 22 ⋅ b 22 ) C = \begin{pmatrix}c_{11} & c_{12} \\c_{21} & c_{22}\end{pmatrix}= \begin{pmatrix} a_{11} \cdot b_{11} + a_{12} \cdot b_{21} & a_{11} \cdot b_{12} + a_{12} \cdot b_{22} \\ a_{21} \cdot b_{11} + a_{22} \cdot b_{21} & a_{21} \cdot b_{12} + a_{22} \cdot b_{22} \end{pmatrix} C=(c11c21c12c22)=(a11b11+a12b21a21b11+a22b21a11b12+a12b22a21b12+a22b22)

在代码中,我们通过三重循环来计算每个 C i j C_{ij} Cij 的值,即 c i j c_{ij} cij​。

模板如下:

void mul(int a[][K], int b[][m])
{
    int c[N][M] = {0};
    
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < m; ++j)
            for (int k = 0; k < K; ++k)
                c[i][j] = (c[i][j] + a[i][k] * b[k][j]) % mod;
    memcpy(a, c, sizeof c);
}

练习:斐波那契

image-20240406111845293

🍬题目链接:斐波那契

🍎算法思想

直接使用斐波那契数列递推的一次计算量可能为 2 ∗ 1 0 9 2*10^9 2109,100个测试数据就为 2 ∗ 1 0 11 2*10^{11} 21011,C++每秒最多 1 0 9 10^9 109计算量,所以不能直接使用递推进行计算。

斐波那契数列可以使用矩阵乘法来进行迭代计算。设矩阵 A ( 0 ) A(0) A(0) 表示斐波那契数列的前两项:

A ( 0 ) = ( 0 1 ) A(0) = \begin{pmatrix} 0 & 1 \end{pmatrix} A(0)=(01)

我们知道斐波那契数列的递推关系式为 f ( n ) = f ( n − 1 ) + f ( n − 2 ) f(n) = f(n-1) + f(n-2) f(n)=f(n1)+f(n2),可以表示为矩阵形式:

( f ( n ) f ( n + 1 ) ) = ( f ( n − 1 ) f ( n ) ) ( 0 1 1 1 ) \begin{pmatrix} f(n) & f(n+1) \end{pmatrix}= \begin{pmatrix} f(n-1) & f(n) \end{pmatrix} \begin{pmatrix} 0 & 1 \\ 1 & 1 \end{pmatrix} (f(n)f(n+1))=(f(n1)f(n))(0111)
因此,我们可以得到如下的矩阵迭代公式:

( f ( n ) f ( n + 1 ) ) = ( f ( n − 1 ) f ( n ) ) ( 0 1 1 1 ) = A ( 0 ) ( 0 1 1 1 ) n = ( 0 1 ) ( 0 1 1 1 ) n \begin{pmatrix} f(n) & f(n+1) \end{pmatrix}= \begin{pmatrix} f(n - 1) & f(n) \end{pmatrix} \begin{pmatrix} 0 & 1 \\ 1 & 1\end{pmatrix}= A(0) \begin{pmatrix} 0 & 1 \\ 1 & 1\end{pmatrix}^n= \begin{pmatrix} 0 & 1 \end{pmatrix} \begin{pmatrix} 0 & 1 \\ 1 & 1 \end{pmatrix}^n (f(n)f(n+1))=(f(n1)f(n))(0111)=A(0)(0111)n=(01)(0111)n
这个公式表示,要得到斐波那契数列的第 n n n 项,只需将初始矩阵 A ( 0 ) A(0) A(0) 与矩阵 ( 0 1 1 1 ) \begin{pmatrix} 0 & 1 \\ 1 & 1 \end{pmatrix} (0111)相乘 n n n​ 次,最终得到的结果的第一个元素即为第n个斐波那契数列元素。

满足结合律的矩阵乘法可以使用快速幂思想快速求矩阵乘积 ( 0 1 1 1 ) n \begin{pmatrix} 0 & 1 \\ 1 & 1 \end{pmatrix}^n (0111)n,所以可以以时间复杂度为 8 l o g n 8logn 8logn​的时间复杂度求出结果,本题目使用此方法大约计算量为5w次计算量,随便就能通过。

🍊具体实现

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int mod = 10000;

// 矩阵乘法函数
void mul(int a[][2], int b[][2])
{
    int c[2][2] = {0}; // 用于存储乘积结果的临时矩阵
    
    // 矩阵乘法:a * b = c
    for (int i = 0; i < 2; ++i)
        for (int j = 0; j < 2; ++j)
            for (int k = 0; k < 2; ++k)
                c[i][j] = (c[i][j] + a[i][k] * b[k][j]) % mod; // 求和并取模
    memcpy(a, c, sizeof c); // 将乘积结果拷贝回矩阵 a
}

// 计算斐波那契数列的第 n 项
int fib(int n)
{
    int a[2][2] = {0, 1, 0, 0}; // 初始矩阵 [F(1), F(0)]
    int f[2][2] = {0, 1, 1, 1}; // 递推矩阵 [F(n), F(n-1)]

    // 使用快速幂加速矩阵乘法
    while (n)
    {
        if (n & 1) mul(a, f); // 如果 n 是奇数,将 a 与 f 相乘
        mul(f, f); // 将 f 自乘,相当于 f^2
        n >>= 1; // 右移一位,相当于 n/2
    }
    return a[0][0]; // 返回斐波那契数列的第 n 项
}

int main()
{
    int n;
    
    // 循环读入 n,直到输入为 -1
    while (cin >> n, n != -1)
        cout << fib(n) << endl; // 输出斐波那契数列的第 n 项
    return 0;
}


七、组合计数


复习

  • 求组合数I :高查询,小范围(底数为 1 0 3 10^3 103级别),直接用 C [ a ] [ b ] = C [ a − 1 ] [ b ] + C [ a − 1 ] [ b − 1 ] C[a][b] = C[a - 1][b] + C[a - 1][b - 1] C[a][b]=C[a1][b]+C[a1][b1]​递推,预处理出全部结果,时间复杂度O(n^2)
const int N = 2010, mod = 1e9 + 7;

int C[N][N];

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()
{
    int n;
    scanf("%d", &n);
    init();
    while (n--)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        printf("%d\n", C[a][b]);
    }
    return 0;
}
  • 求组合数II:中数据范围(底数为 1 0 5 10^5 105级别),较高访问,取模的数为质数,预处理出所有需要用的阶乘及其逆元,按照定义求解即可
typedef long long LL;

const int N = 100010, mod = 1e9 + 7;

int fact[N], infact[N];

int qmi(int a, int k)
{
    int ret = 1;
    while (k)
    {
        if (k & 1) ret = (LL)ret * a % mod;
        a = (LL)a * a % mod;
        k >>= 1;
    }
    return ret;
}

int main()
{
    int n;
    scanf("%d", &n);
    
    fact[0] = 1;
    infact[0] = 1;
    for (int i = 1; i <= 100000; ++i)
    {
        fact[i] = (LL)fact[i - 1] * i % mod;
        infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2) % mod;
    }
    
    while (n--)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        
        int res = (LL)fact[a] * infact[b] % mod * infact[a - b] % mod;
        printf("%d\n", res);
    }
    return 0;
}
  • 求组合数III:大数据范围(底数为 1 0 18 10^{18} 1018级别),低访问,模的数为质数且数据较小,利用卢卡斯定理进行递推 C [ a ] [ b ] = C [ a % p ] [ b % p ] ∗ C [ a / p ] [ b / p ] % p C[a][b] = C[a \% p][b \% p] * C[a / p][b / p] \% p C[a][b]=C[a%p][b%p]C[a/p][b/p]%p
typedef long long LL;

int p;

int qmi(int a, int k)
{
    int ret = 1;
    while (k)
    {
        if (k & 1) ret = (LL)ret * a % p;
        a = (LL)a * a % p;
        k >>= 1;
    }
    return ret;
}

int C(int a, int b)
{
    if (b > a) return 0;

    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 lucas(LL a, LL b)
{
    if (a < p && b < p) return C(a, b);
    return (LL)C(a % p, b % p) * lucas(a / p, b / p) % p;
}

int main()
{
    int n;
    cin >> n;

    while (n--)
    {
        LL a, b;
        cin >> a >> b >> p;

        cout << lucas(a, b) << endl;
    }
    return 0;
}
  • 求组和数IV:较小数据(底数为 1 0 3 10^3 103级别),但是没有取模,结果数字很大,需要高精度算法
// 求组和数IV:较小数据,但是没有取模,结果数字很大,需要高精度算法
// 最简单的想法:实现高精度乘法和除法,按照定义进行组合数计算即可。
// 但是,实现两种高精度算法比较麻烦,我们可以将定义式分子分母分解质因数,将上下分子分母的质因数约去,只用求分子剩下的质因数的乘积
// a!中质因数b的个数为 a/b + a/(b^2) + a/(b^3) + a/(b^4) + ......
// 由于分子和分母本来乘数个数就相等,再加上分子的乘数较大,分子的质因数是完全包含分母的质因数的,具体证明这里略

const int N = 5010;

int primes[N], cnt;
bool book[N];
int sum[N];

void get_primes(int n)
{
    for (int i = 2; i <= n; ++i)
    {
        if (!book[i]) primes[cnt++] = i;

        for (int j = 0; primes[j] <= n / i; ++j)
        {
            book[i * primes[j]] = true;
            if (i % primes[j] == 0) break;
        }
    }
}

vector<int> mul(vector<int>& v, const int p)
{
    vector<int> c;
    int t = 0;

    for (int i = 0; i < v.size(); ++i)
    {
        t += v[i] * p;
        c.push_back(t % 10);
        t /= 10;
    }

    while (t)
    {
        c.push_back(t % 10);
        t /= 10;
    }

    return c;
}

int get(int a, int p)
{
    int res = 0;
    while (a)
    {
        res += a / p;
        a /= p;
    }
    return res;
}

int main()
{
    int a, b;
    cin >> a >> b;

    get_primes(a);

    for (int i = 0; i < cnt; ++i)
    {
        sum[i] = get(a, primes[i]) - get(b, primes[i]) - get(a - b, primes[i]);
    }

    vector<int> res(1, 1);
    for (int i = 0; i < cnt; ++i)
    {
        for (int j = 0; j < sum[i]; ++j)
        {
            res = mul(res, primes[i]);
        }
    }

    for (auto rit = res.rbegin(); rit != res.rend(); ++rit)
    {
        cout << *rit;
    }

    return 0;
}

练习:计算系数

image-20240406125658142

🍬题目链接:计算系数

🍎算法思想

首先,先复习一下二项式定理:

二项式定理的思想在于展开一个二项式的幂,即如何计算类似 ( a + b ) n (a + b)^n (a+b)n 的表达式。其核心思想涉及两个方面:

  1. 二项式的多项式展开:二项式是两个项的和,而二项式定理告诉我们如何将一个二项式的幂展开为多个项的和。例如, ( a + b ) n (a + b)^n (a+b)n 可以展开为多个项的和,其中每个项的形式都是 a m ⋅ b k a^m \cdot b^k ambk,其中 m + k = n m + k = n m+k=n

  2. 组合数的应用:在展开中,每个项的系数是由组合数 C ( n , k ) C(n, k) C(n,k)确定的,表示从 n n n 个不同元素中选择 k k k 个元素的方式数。这是因为在展开 ( a + b ) n (a + b)^n (a+b)n 中, a a a 的幂从 n n n 递减到 0 0 0,而 b b b 的幂从 0 0 0 递增到 n n n,每一项的幂次之和都是 n n n,所以需要计算每一项的系数,即组合数。

因此,二项式定理的思想是利用组合数的性质,按照特定的规则将二项式的幂展开为一系列项的和,每个项的幂次之和等于原始幂,并且每个项的系数是由组合数确定的。这使得我们能够轻松地处理二项式的高次幂,从而简化了许多代数计算。

这道题就是使用二项式定理求 x n y m x^ny^m xnym这一项的系数—— C k n ∗ a n ∗ b m % 10007 C^n_k*a^n*b^m\%10007 Cknanbm%10007

由于这道题的底数范围只有1000,所以直接使用上面复习的求组合数的方法一,暴力求解即可,复习一下这种暴力做法的思想:

给定一个 C ( n , k ) C(n, k) C(n,k),它可以通过以下递推公式计算:

C ( n , k ) = C ( n − 1 , k − 1 ) + C ( n − 1 , k ) C(n, k) = C(n-1, k-1) + C(n-1, k) C(n,k)=C(n1,k1)+C(n1,k)

其中 C ( n , k ) C(n, k) C(n,k) 表示从 n n n 个元素中选择 k k k 个元素的组合数。这个递推公式的意义在于,一个组合数可以由其上方的两个组合数的和来计算得到。这个递推公式也是由组合数的性质推导出来的。

🍊具体实现

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010, mod = 10007;

typedef long long LL;

int C[N][N];

// 快速幂求a^n和b^m
int quick_power(int a, int k, int p)  // 求a^k mod p
{
    int res = 1 % p;
    while (k)
    {
        if (k & 1) res = (LL)res * a % p;
        a = (LL)a * a % p;
        k >>= 1;
    }
    return res;
}

int main()
{
    int a, b, k, n, m;
    cin >> a >> b >> k >> n >> m;
    
    // 暴力组合数递推
    for (int i = 0; i <= k; ++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;
    
    cout << C[k][n] * quick_power(a, n, mod) % mod * quick_power(b, m, mod) % mod << endl;
    return 0;
}

总结


本周我们复习了:

  • 区间DP
  • 树形DP
  • 快速幂
  • 最大公约数
  • 分解质因数
  • 矩阵乘法
  • 组合计数

如果解析有不对之处还请指正,我会尽快修改,多谢大家的包容。

如果大家喜欢这个系列,还请大家多多支持啦😋!

如果这篇文章有帮到你,还请给我一个大拇指 👍和小星星 ⭐️支持一下白晨吧!喜欢白晨【蓝桥杯】系列的话,不如关注👀白晨,以便看到最新更新哟!!!

我是不太能熬夜的白晨,我们下篇文章见。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1573796.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深入浅出 -- 系统架构之Keepalived搭建双机热备

Keepalived重启脚本双机热备搭建 ①首先创建一个对应的目录并下载keepalived安装包&#xff08;提取码:s6aq&#xff09;到Linux中并解压&#xff1a; [rootlocalhost]# mkdir /soft/keepalived && cd /soft/keepalived [rootlocalhost]# wget https://www.keepalived.…

ROS2 采集虚拟仿真环境图像并发布

简介&#xff1a;ROS2功能的学习我们还是在基于OpenAI的gym虚拟仿真环境中来完成&#xff0c;gym虚拟仿真环境安装请参考另一篇教程&#xff0c;这里不再重复说明&#xff0c;接下来我们开始创建一个ROS2的功能节点&#xff0c;并发布虚拟仿真环境小车摄像头的图像&#xff0c;…

Android Studio 打开Local Changes界面

在编写代码的过程中&#xff0c;经常要回顾本地仓库做了那些修改。打开Local Changes界面&#xff0c;能做到一目了然&#xff0c;不用再去使用git命令查看。 File->Settings->Version control->Commit 把Use non-modal commit interface 选项 取消勾选 即可

20240403在ubuntu20.04下解压缩gz压缩包

20240403在ubuntu20.04下解压缩gz压缩包.txt 2024/4/3 15:17 缘起&#xff1a;使用友善之臂FriendlyElec的NanoPi NEO Core开发板 https://wiki.friendlyelec.com/wiki/index.php/NanoPi_NEO/zh#.E8.BF.90.E8.A1.8CFriendlyCore NanoPi NEO/zh http://wiki.friendlyelec.com/w…

Java基于微信小程序的校园外卖平台系统,附源码

博主介绍&#xff1a;✌IT徐师兄、7年大厂程序员经历。全网粉丝15W、csdn博客专家、掘金/华为云//InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3…

蓝桥杯刷题 前缀和与差分-[NewOJ P1819]推箱子(C++)

题目描述 在一个高度为H的箱子前方&#xff0c;有一个长和高为N的障碍物。 障碍物的每一列存在一个连续的缺口&#xff0c;第i列的缺口从第l各单位到第h个单位&#xff08;从底部由0开始数&#xff09;。 现在请你清理出一条高度为H的通道&#xff0c;使得箱子可以直接推出去。…

同通俗易的理解 ADC

理解什么是ADC 文章目录 1、通俗理解什么是ADC 2、什么是ADC 3、ADC的采样率 4、采样位数 5、采样精度 ADC实际没有这么的简单&#xff0c;深入了解需要去学各种寄存器之间如何协作&#xff0c;信号如何走通。这些概念在后面会有讲解。 1、通俗理解…

UE4_X光效果设置_法线图影响透明度

UE4_X光效果设置_法线图影响透明度 2019-03-22 13:37 Exponentin 设置轮廓光扩散度 baseReflectFactionIn 设置内部黑色的亮度值。nromal&#xff0c;连接应用一张法线图&#xff0c;Lerp两色插值&#xff0c;给两个数值&#xff0c;制造一个渐变。 法线图影响透明度&#xf…

harbor机器断电之后服务正常,但是不能访问问题

1.进到harbor目录查看harbor服务是否正常 2.检查监听端口 3.检查防火墙 4.检查ip端口转发&#xff08;我这里刚刚开启&#xff0c;之前是关闭的。 1是开起&#xff0c;0是关闭&#xff09; 5.改为之后重启就可以正常访问了

文章解读与仿真程序复现思路——电力系统自动化EI\CSCD\北大核心《提升光储充电站运行效率的多目标优化配置策略》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

Redis的配置文件详解

单位&#xff1a;Redis配置对大小写不敏感&#xff01; 注意这里&#xff1a;任何写法都可&#xff0c;不区分大小写。 units are case insensitive so 1GB 1Gb 1gB are all the same.包含&#xff1a;搭建Redis集群时&#xff0c;可以使用includes包含其他配置文件网络&…

泰迪·南通师范大数据智能工作室挂牌签约仪式圆满结束

为促进毕业生高质量就业&#xff0c;拓宽就业渠道&#xff0c;加强校企合作&#xff0c;4月2日&#xff0c;泰迪智能科技股份有限公司上海分公司总经理彭艳昆一行来校出席南通师范高等专科学校“泰迪科技南通师范大数据智能工作室”签约揭牌仪式。学校党委副书记陈玉君、科技处…

瑞吉外卖实战学习--16、登录短信验证

登录短信验证 前言环境准备(根据mybatisPlus 规范实体类和接口)1、User实体类2、mapper文件3、service文件4、impl文件5、随机生成验证码的工具类6、发送验证码的工具类7、获取验证码和移动端登录前言 本项目gitee位置:gitee网址 本项目采用的技术是:springboot + mybatis…

c++虚函数表中的内存布局

c虚函数表中的内存布局 1.Class的内存分布2.其他修改Class中变量的方法3.通过虚函数表内存偏移调用虚函数4.继承状态下的虚函数表内存5.派生类函数中多出来的虚函数访问("基类指针指向子类对象") 1.Class的内存分布 #include <iostream>using namespace std;#…

ThreadLocal核心源码阅读

1. 概述 ThreadLocal为每个使用该变量的线程提供独立的变量副本&#xff0c;因此每一个线程都可以独立地改变自己的副本&#xff0c;而不会影响其他线程。 入门例子&#xff1a; public class ThreadLocalStudy {static ThreadLocal<String> stringThreadLocal new T…

Python爬取公众号封面图(零基础也能看懂)

&#x1f4da;博客主页&#xff1a;knighthood2001 ✨公众号&#xff1a;认知up吧 &#xff08;目前正在带领大家一起提升认知&#xff0c;感兴趣可以来围观一下&#xff09; &#x1f383;知识星球&#xff1a;【认知up吧|成长|副业】介绍 ❤️感谢大家点赞&#x1f44d;&…

刷题之Leetcode209题(超级详细)

209.长度最小的子数组 力扣题目链接(opens new window)https://leetcode.cn/problems/minimum-size-subarray-sum/ 给定一个含有 n 个正整数的数组和一个正整数 s &#xff0c;找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组&#xff0c;并返回其长度。如果不存在符合条…

地面站Mission Planner从源码编译与运行

0. 环境 - win10&#xff08;基本需要100G硬盘&#xff09; - ubuntu18 1. 安装vs2022 下载 vs2022 community 在线安装包。 https://visualstudio.microsoft.com/ 打开 Visual Studio Installer 先安装 Visual Studio Community 2022本体。占用1.2GB。 Visual Studio Inst…

树状数组相关题目

题目一 方法一 归并分治 代码&#xff1a; # include <stdio.h>int arr[100]; int help[100];int n;//归并分治 // 1.统计i、j来自 l~r 范围的情况下&#xff0c;逆序对数量 // 2.统计完成后&#xff0c;让arr[l...r]变成有序的 int f(int l, int r) {if (l r)return…

html写一个登录注册页面

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>注册登录界面Ⅰ</title><link rel"stylesheet" href"https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.mi…