本题目的题解写得较长,照顾一些初中同学(虽然我自己也是初中生),同时也方便作者以后复习使用。如有错误或疑问,请尽快私信作者
题目概况
洛谷链接: https://www.luogu.com.cn/problem/P2822
计蒜客链接: https://nanti.jisuanke.com/t/T2034
**难度:**普及+/提高(计蒜客评级普及T3 ,是不是低了 )
题目分析
简化题目: 定义一个数组求出C[n][min(n, m)]
中满足C(m, n)
是k
的倍数的有多少
涉及知识点: 前缀和以及杨辉三角,组合数的相关公式等数学预备知识
解题思路:
在讲思路之前,我先简单说一下预备知识供大家参考:
一、组合数
数据范围较大,阶乘计算不现实,在这里给出一个关于组合数的递推公式(本题只涉及 例1,例 2各位看官有兴趣可以了解一下)
对,就是高中数学选修二里的那玩意(翻教材去咯~)
我们也可以把该式变形为:C(m, n) = C(m, n - 1) + C(m - 1,n - 1)
所以用代码实现,递推式即为c[i][j] = c[i - 1][j] + c[i - 1][j - 1]
二、杨辉三角![在这里插入图片描述](https://img-blog.csdnimg.cn/8038e3bb930b4d3397985eaed2fbf676.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpZ2h0bmluZ0NT,size_16,color_FFFFFF,t_70)
形如此图即为杨辉三角,应该都听过,递推式即为dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1]
三、解题思路概括
首先我们发现组合数递推公式和杨辉三角递推公式相同,也就是说如果抽象的组合数我们无法想明白,就把它理解为杨辉三角做一个前缀和的预处理,但要注意,在最朴素的杨辉三角中,最右侧的元素无法更新,我们需要通过继承的方式从上到上更新。这个组合数我们可以在过程中直接模k,如果模k等于0,就把数量在前缀和中加1。
代码要点拆解
一、预处理环节
直接看注释
二维前缀和递推公式:
注意区分 :区域是前缀和的区域; 子矩阵指的是原矩阵中的区域
令x2 = n, y2 = m,假设A区域等于A子矩阵(视同)就是(1,1)。我们要输出的是D区域的,但是:
D = C区域(A,C子矩阵) + B区域(A,B子矩阵)
所以在加上B,C区域后,我们需要把A区域再减一遍,去重。
//预处理
c[0][0] = c[1][0] = c[1][1] = 1; //三角上第0行和第1行需要预定义(类比组合数)
for (int i = 2; i <= 2000; i++) {
c[i][0] = 1; //杨辉三角的每行第一列都是1,也就是C(0, i) = 1
for (int j = 1; j <= i; j++) {
c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % k; //组合数递推
ans[i][j] = ans[i][j - 1] + ans[i - 1][j] - ans[i - 1][j - 1]; //二维前缀和递推
if (c[i][j] == 0) { //如果这个组合数模K后得到的余数是0
ans[i][j]++;
}
}
ans[i][i + 1] = ans[i][i]; //最右侧的继承,直接把上一行的最右侧搬下来更新
}
二、输出环节
输出格式里有,m
有可能大于n
,所以我们需要min(n, m)
while (t--) {
scanf("%d%d", &n, &m);
printf("%lld\n", ans[n][min(n, m)]);
}
完整代码
#include <iostream>
#include <cstdio>
#define ll long long int
using namespace std;
const int MAXN = 2005;
int n, m, t, k; //见题面
ll ans[MAXN][MAXN], c[MAXN][MAXN]; //答案数组和组合数存储数组
int main() {
//freopen("problem.in", "r", stdin);
//freopen("problem.out", "w", stdout);
scanf("%d%d", &t, &k);
//预处理
c[0][0] = c[1][0] = c[1][1] = 1;
for (int i = 2; i <= 2000; i++) {
c[i][0] = 1;
for (int j = 1; j <= i; j++) {
c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % k; //组合数递推
ans[i][j] = ans[i][j - 1] + ans[i - 1][j] - ans[i - 1][j - 1]; //前缀和
if (c[i][j] == 0) {
ans[i][j]++;
}
}
ans[i][i + 1] = ans[i][i];
}
while (t--) {
scanf("%d%d", &n, &m);
printf("%lld\n", ans[n][min(n, m)]);
}
return 0;
}