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

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

在这里插入图片描述

文章目录

  • 前言
  • 蓝桥杯复习(四)
    • 一、区间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] >= ar = mid;elsel = 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] <= al = mid;elser = 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 = cfor (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^2n >>= 1; // 右移一位,相当于 n/2}return a[0][0]; // 返回斐波那契数列的第 n 项
}int main()
{int n;// 循环读入 n,直到输入为 -1while (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.hqwc.cn/news/597069.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

postgresql数据库|数据整合的好工具--Oracle-fdw的部署和使用

概述 Oracle_fdw 是一种postgresql外部表插件&#xff0c;可以读取到Oracle上面的数据。是一种非常方便且常见的pg与Oracle的同步数据的方法 Oracle_fdw 适用场景&#xff1a; Oracle_fdw 是一个开源的 Foreign Data Wrapper (FDW)&#xff0c;主要用于在 PostgreSQL 数据库中…

LED点阵屏与LCD1602

目录 LED点阵屏 点阵屏的介绍 LED点阵屏分类 点阵屏的显示原理 点阵案例 静态案例 电路图 keil文件 动态案例 电路图 keil文件 LCD1602 LCD1602概述 LCD1602内部结构 存储器结构 LCD引脚及应用电路 时序结构 LCD1602指令集 LCD1602编程 初始化 显示字符 …

【Web应用技术基础】JavaScript(6)——案例:留言墙

视频已发。截图如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</titl…

linux------jekins构建cicd

&#x1f388;个人主页&#xff1a;靓仔很忙i &#x1f4bb;B 站主页&#xff1a;&#x1f449;B站&#x1f448; &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;linux &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#…

并发编程三大特性之原子性

一、并发编程3大特性是什么&#xff1f; 并发编程三大特性分别是原子性、可见性和有序性。java内存模型JMM就是围绕着原子性、 可见性、原子性来处理java线程间通信的。 二、原子性 1、什么是原子性&#xff1f; 原子性是指一个操作是不可分割的&#xff0c;不可终端的&#xf…

想要孩子对你敞开心扉,就别再做这件事情了

短篇日记 今天晚上&#xff0c;发生了几件小事&#xff0c;让我明白&#xff0c;与孩子真诚沟通比说教要强一万倍。 第一件事情&#xff1a;晚上我下班回来&#xff0c;俩宝都在小区边玩边等我。 玩儿了一会儿觉得有点冷&#xff0c;我们就打算回家。 回家途中&#xff0c;…

B/S结构和C/S结构详细介绍

文章目录 什么是c/s结构、b/s结构c/s结构b/s结构 b/s结构和c/s结构各自的优点&#xff1a;数据放在服务端和客户端的利与弊&#xff1f;c/s、b/s区别&#xff1a; 什么是c/s结构、b/s结构 1、C/S结构&#xff0c;即Client/Server(客户机/服务器)结构&#xff0c;是大家熟知的软…

【智能排班系统】基于SpringSecurity实现登录验证、权限验证

文章目录 SpringSecurity介绍sss-security实现依赖工具类Jwt工具JSON响应工具加密工具类 用户上下文用户信息实体类用户上下文 自定义重写自定义无权限的报错自定义密码加密自定义用户类 过滤器登录过滤器权限过滤器 Service登录Service 配置类说明登录验证权限验证IP流量限制 …

【数据分析面试】12. 随机抽取颜色球(Python random模块应用:choices()/choice()/sample())

题目 随机抽取颜色球 编写一个函数来模拟从罐子中抽取球的过程。球的颜色存储在名为jar的列表中&#xff0c;每个颜色对应球的数量存储在名为n_balls的列表中&#xff0c;且数量与颜色列表的索引对应。 示例&#xff1a; 输入&#xff1a; jar [green, red, blue] n_bal…

【XCPC笔记】2023 (ICPC) Jiangxi Provincial Contest——ABCHIJKL 做题记录

赛后gym练习及补题&#xff0c;gym链接&#xff1a;2023 (ICPC) Jiangxi Provincial Contest – Official Contest 补题顺序 L [Zhang Fei Threading Needles - Thick with Fine](https://codeforces.com/gym/104385/problem/L)题面解读参考代码 A [Drill Wood to Make Fire](h…

Ceph学习 - 2.分布式文件系统DFS

文章目录 1.分布式文件系统DFS1.1 DFS简介1.1.1 存储基础1.1.2 分布式文件系统1.1.3 DSS简介1.1.4 常见的文件系统 1.2 原理解读1.2.1 分布式数据存储1.2.2 存储角色1.2.3 数据高可用 1.3 小结 1.分布式文件系统DFS 学习目标&#xff1a;这一节&#xff0c;我们从DFS简介、原理…

ENSP防火墙配置内网NAT访问外网,内网发表web服务器

内网配置NAT访问外网 搭建拓扑 基础配置 cloud配置 配置防火墙web登录&#xff0c;配置web和设备命令行过期时间 Username:admin Password: // Admin123 The password needs to be changed. Change now? [Y/N]: Y Please enter old password: // Admin123 Please enter …