1 条题解

  • 0
    @ 2026-10-26 5:03:37

    思路与算法

    按题图,上方有 bb 行、每行 aa 格,下方有 dd 行、每行 a+ca+c 格。固定上方放 ii 个车,下方放 r=k−ir=k-i 个车。

    先从上方 bb 行选出 ii 行,再向这些行分配互异的 aa 个可用列,方法数为 (bi)P(a,i)\binom bi P(a,i)。这些车占用了 ii 个左侧列;所有下方长行都包含它们,所以底部剩余可用列恰为 a+c−ia+c-i。从底部选 rr 行并分配不同列,方法数为 (dr)P(a+c−i,r)\binom dr P(a+c-i,r)。答案是

    $$\sum_{i=0}^{\min(k,a,b)}\binom bi P(a,i)\binom d{k-i}P(a+c-i,k-i)\pmod{100003}.$$

    不合法的组合数或排列数定义为0。预处理至 H=max⁡(a+c,b,d)≤2000H=\max(a+c,b,d)\le2000 的阶乘及逆阶乘;H<100003H<100003 且模数为素数,所有需要求逆的阶乘均可逆。

    正确性说明

    任一合法放置有唯一的上方车数 ii。选定上部行后,每行恰有一车,列必须互异,因此为这些已确定的行指定互异列对应 P(a,i)P(a,i) 种位置。底部所有行都比上部宽,恰能使用除去已占 ii 列后的其余 a+c−ia+c-i 列,因此底部计数公式完整且不依赖上部占列的具体形状。

    两部分行互不重合,剩余列选择保证全棋盘列也不冲突。反过来,式中每种选择均给出唯一合法车的位置集合,无需再给相同的车编号。按 ii 的分类互不重叠,求和覆盖所有方案。阶乘计算公式与模逆条件成立,输出正确。

    复杂度

    预处理和初始化为 O(H)O(H),一次快速幂 O(log⁡P)O(\log P),枚举不超过 k+1k+1 个上部车数,每项常数时间。总时间 O(H+log⁡P+k)O(H+\log P+k),额外空间 O(H)O(H),P=100003P=100003。不枚举棋盘格子或车的全排列。

    易错点

    1. 上部是 aa 列 bb 行,下部是 a+ca+c 列 dd 行,不能只把棋盘看成两个互不影响的矩形。
    2. 底部可用列须减去上部已占列数 ii。
    3. 车不编号,选行后指定列已决定方案,不再乘 (ki)\binom ki 或 k!k!。
    4. 若 k−i>dk-i>d 或剩余列不足,该项为0,避免负下标访问。
    5. 覆盖最小正尺寸、最大尺寸、Hall约束刚好可行的瘦长棋盘。候选的零尺寸不满足当前提示中所有参数≥1,整点拒用且原包保留。独立答案为逐行Ferrers棋盘DP,并与小棋盘完整车位置枚举核对。

    参考代码(C++11)

    #include <bits/stdc++.h>
    using namespace std;
    using ll = long long;
    const int MOD = 100003;
    ll power(ll a, ll e) {
        ll r = 1;
        while (e) {
            if (e & 1)
                r = r * a % MOD;
            a = a * a % MOD;
            e >>= 1;
        }
        return r;
    }
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        int a, b, c, d, k;
        cin >> a >> b >> c >> d >> k;
        int size = max(a + c, max(b, d));
        vector<ll> fact(size + 1), inv(size + 1);
        fact[0] = 1;
        for (int i = 1; i <= size; i++)
            fact[i] = fact[i - 1] * i % MOD;
        inv[size] = power(fact[size], MOD - 2);
        for (int i = size; i; i--)
            inv[i - 1] = inv[i] * i % MOD;
        auto C = [&](int n, int r) -> ll {
            if (r < 0 || r > n)
                return 0;
            return fact[n] * inv[r] % MOD * inv[n - r] % MOD;
        };
        auto P = [&](int n, int r) -> ll {
            if (r < 0 || r > n)
                return 0;
            return fact[n] * inv[n - r] % MOD;
        };
        ll answer = 0;
        for (int upper = 0; upper <= min(k, min(a, b)); upper++) {
            int lower = k - upper;
            ll ways =
                C(b, upper) * P(a, upper) % MOD * C(d, lower) % MOD * P(a + c - upper, lower) % MOD;
            answer = (answer + ways) % MOD;
        }
        cout << answer << '\n';
    }
    
    • 1

    信息

    ID
    1667
    时间
    1000ms
    内存
    512MiB
    难度
    普及+
    标签
    递交数
    1
    已通过
    1
    上传者