[luogu p2282] 组合数问题

传送门

组合数问题

题目描述

组合数 \(\binom{n}{m}\) 表示的是从 \(n\) 个物品中选出 \(m\) 个物品的方案数。举个例子,从 \((1,2,3)\) 三个物品中选择两个物品可以有 \((1,2),(1,3),(2,3)\) 这三种选择方法。根据组合数的定义,我们可以给出计算组合数 \(\binom{n}{m}\) 的一般公式:

\[\binom{n}{m}=\frac{n!}{m!(n-m)!} \]

其中 \(n!=1\times2\times\cdots\times n\);特别地,定义 \(0!=1\)

小葱想知道如果给定 \(n,m\)\(k\),对于所有的 \(0\leq i\leq n,0\leq j\leq \min \left ( i, m \right )\) 有多少对 \((i,j)\) 满足 \(k|\binom{i}{j}\)

输入输出格式

输入格式

第一行有两个整数 \(t,k\),其中 \(t\) 代表该测试点总共有多少组测试数据,\(k\) 的意义见问题描述。

接下来 \(t\) 行每行两个整数 \(n,m\),其中 \(n,m\) 的意义见问题描述。

输出格式

\(t\) 行,每行一个整数代表所有的 \(0\leq i\leq n,0\leq j\leq \min \left ( i, m \right )\) 中有多少对 \((i,j)\) 满足 \(k|\binom{i}{j}\)

输入输出样例

输入样例 #1

1 2
3 3

输出样例 #1

1

输入样例 #2

2 5
4 5
6 7

输出样例 #2

0
7

说明

【样例1说明】

在所有可能的情况中,只有 \(\binom{2}{1} = 2\) 一种情况是 \(2\) 的倍数。

【子任务】

  • 对于全部的测试点,保证 \(0 \leq n, m \leq 2 \times 10^3\)\(1 \leq t \leq 10^4\)

分析

这题一看就要用 \(\operatorname{O}(n ^ 2)\) 预处理 \(\operatorname{O}(1)\) 回答的算法,那么具体怎么做呢?

首先用杨辉三角预处理出2000内所有的组合数对 \(k\) 取模的结果。如下:

void init(int k) {
    c[0][0] = c[1][0] = c[1][1] = 1;
    for (int i = 2; i < maxn; ++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;
        }
    }
}

然后仔细观察题目要求什么:

对于所有的 \(0\leq i\leq n,0\leq j\leq \min \left ( i, m \right )\) 有多少对 \((i,j)\) 满足 \(k|\binom{i}{j}\)

为了方便讨论,若 \(m > n\),我们一律令 \(n \rightarrow m\)

定义 \(ans_{i, j}\) 为答案数组,用二维前缀和得到公式:

\[ans_{i, j} = ans_{i - 1, j} + ans_{i, j - 1} - ans_{i - 1, j - 1} \]

当然,如果我们发现 \(k|\binom{i}{j}\),那么 \(ans_{i, j}\) 还需自增 \(1\)

我们就可以写出这样的代码:

void init(int k) {
    c[0][0] = c[1][0] = c[1][1] = 1;
    for (int i = 2; i < maxn; ++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 - 1][j] + ans[i][j - 1] - ans[i - 1][j - 1];
            if (c[i][j] == 0) ++ans[i][j];
        }
    }
}

然而就这样结束了吗?

如果这样,提交程序,你会发现你的代码能拿到15分的好成绩

这是为什么呢?

不妨来看看当 \(k = 5\) 时,ans的表如何。(此处列举了前10*10个)

0MkJMj.png

显然,绿色方块的值是红+蓝-紫。但是你发现没有,红色方块的所在位置已经超出了杨辉三角的预处理范围。(橙色左下方为预处理范围,右上角为非预处理范围),也就是当 \(i = j\) 时,\(ans_{i - 1, j}\) 没有被处理。

但是,这个东西其实很好处理,因为 \(ans_{i+1, i} = ans_{i, i}\)。至于这是为什么,见其定义:

对于所有的 \(0\leq i\leq n,0\leq j\leq \min \left ( i, m \right )\) 有多少对 \((i,j)\) 满足 \(k|\binom{i}{j}\)

观察到这个 \(min(i, m)\) 了吗?这个的意思就是说,当 \(m > n\) 时,\(ans_{m, n}\)\(ans _{n, n}\) 其实是等价的。

同样的道理便可以得到 \(ans_{i+1, i} = ans_{i, i}\)。因此只要在i循环中最后加一句ans[i][i + 1] = ans[i][i];即可。代码如下:

void init(int k) {
    c[0][0] = c[1][0] = c[1][1] = 1;
    for (int i = 2; i < maxn; ++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 - 1][j] + ans[i][j - 1] - ans[i - 1][j - 1];
            if (c[i][j] == 0) ++ans[i][j];
        }
        ans[i][i + 1] = ans[i][i];
    }
}

这样就可以AC啦~上一个整体代码吧!

代码

/*
 * @Author: crab-in-the-northeast 
 * @Date: 2020-10-01 14:01:33 
 * @Last Modified by: crab-in-the-northeast
 * @Last Modified time: 2020-10-01 16:18:48
 */
#include <iostream>
#include <cstdio>

const int maxn = 2005;
const int maxm = 2005;
long long c[maxn][maxm], ans[maxn][maxm];

void init(int k) {
    c[0][0] = c[1][0] = c[1][1] = 1;
    for (int i = 2; i < maxn; ++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 - 1][j] + ans[i][j - 1] - ans[i - 1][j - 1];
            if (c[i][j] == 0) ++ans[i][j];
        }
        ans[i][i + 1] = ans[i][i];
    }
}

int main() {
    int t, k;
    std :: scanf("%d %d", &t, &k);
    init(k);
    while (t--) {
        int n, m;
        std :: scanf("%d %d", &n, &m);
        std :: printf("%lld\n", ans[n][m > n ? n : m]);
    }
    return 0;
}

评测记录

评测记录

posted @ 2020-10-01 16:20  东北小蟹蟹  阅读(156)  评论(0编辑  收藏