P11250 [GESP202409 八级] 手套配对

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

一、先回答问题

结论

恰好 k 对手套要分三步:选完整对、选剩余单只所在对、再选左右手。

容易误入的路

直接 C(2n,m) 再扣非法情况很容易重叠计数。

二、核心观察

建模与压缩

完整对贡献 2 只;剩余 m-2k 只必须来自不同的剩余对。

为什么不丢答案:三步选择互不重叠,且每种合法取法唯一对应这三步。

完整对 剩余单只 左右选择

三、互动演示

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

四、自己选答案

问题 1

为什么剩余每对只能取一只?

问题 2

什么时候答案为 0?

五、手推结果

选择阶段方案数含义
完整对C(n,k)确定 k 对
单只来源C(n-k,m-2k)不能再成对
左右2^(m-2k)每对二选一

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

六、C++14 参考代码

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

const int MOD = 1e9 + 7;
const int MAXN = 2005;

long long C[MAXN][MAXN];
long long p2[MAXN];

void precompute() {
    // 预处理组合数
    for (int i = 0; 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]) % MOD;
        }
    }
    // 预处理 2 的幂
    p2[0] = 1;
    for (int i = 1; i < MAXN; i++) {
        p2[i] = (p2[i - 1] * 2) % MOD;
    }
}

void solve() {
    int n, m, k;
    cin >> n >> m >> k;
    
    int remaining_gloves = m - 2 * k;
    int remaining_pairs = n - k;
    
    // 如果需要取的手套数少于凑成 k 对所需,或者剩余要取的手套数超过剩余对数
    if (remaining_gloves < 0 || remaining_gloves > remaining_pairs) {
        cout << 0 << "\n";
        return;
    }
    
    long long ans = C[n][k]; // 选出 k 对
    ans = (ans * C[remaining_pairs][remaining_gloves]) % MOD; // 从剩下对里选剩下的手套
    ans = (ans * p2[remaining_gloves]) % MOD; // 每只都有左右两种可能
    
    cout << ans << "\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    precompute();
    
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    
    return 0;
}