P2822 [NOIP 2016 提高组] 组合数问题

GESP8 冲刺课堂页:先建模,再压缩状态,最后读代码。

一、先回答问题

结论

先用杨辉三角取模标记 C(i,j) 是否被 k 整除,再用二维前缀和 O(1) 回答。

容易误入的路

每个询问重新枚举所有 (i,j),T 到 1e4 时会超时。

二、核心观察

建模与压缩

mark[i][j]=1 表示 C(i,j)%k==0,s[i][j] 表示左上矩形里的标记总数。

为什么不丢答案:j>i 的位置记 0,查询区域就能统一变成矩形前缀。

C(i,j)%k mark 前缀和 s

三、互动演示

步骤1
关键状态 A-
关键状态 B-
答案/目标-
复杂度看表
课堂动作手推

四、自己选答案

问题 1

为什么只存取模结果?

问题 2

m>n 怎么办?

五、手推结果

阶段状态作用
组合数c[i][j]杨辉递推取模
标记mark是否整除
查询s矩形计数

课堂要求:每题先说清对象、关系、保留的信息、压缩掉的信息,再写代码。

六、C++14 参考代码

#include <bits/stdc++.h>
using namespace std;

int c[2005][2005], s[2005][2005];
int t, k;

void precompute() {
    // 杨辉三角递推组合数并取模
    c[0][0] = 1 % k;
    for (int i = 1; i <= 2000; i++) {
        c[i][0] = 1 % k;
        for (int j = 1; j <= i; j++) {
            c[i][j] = (c[i-1][j] + c[i-1][j-1]) % k;
        }
    }

    // 计算二维前缀和
    for (int i = 1; i <= 2000; i++) {
        for (int j = 1; j <= 2000; j++) {
            int val = (j <= i && c[i][j] == 0) ? 1 : 0;
            s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + val;
        }
        // 对于这一行的超出 i 的部分,直接继承上一列的值
        // 但其实由于 val 在 j > i 时为 0,上面的公式也适用
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> t >> k;
    precompute();

    while (t--) {
        int n, m;
        cin >> n >> m;
        // 组合数 C(i, j) 中 j 不能大于 i
        if (m > n) m = n;
        cout << s[n][m] << "\n";
    }

    return 0;
}