L 形棋盘拆成上下两个左对齐矩形,按上矩形的车数 i 分类:上矩形 C(b,i)·P(a,i),下矩形可用列数被扣成 a+c-i,贡献 C(d,k-i)·P(a+c-i,k-i),求和即答案。
OJ: roj
题目 ID: 1654
难度:普及+/提高-
标签:组合计数组合数学数学一本通python
创建: 2026-10-01 00:18
更新: 2026-10-07 13:50
形式化题目
棋盘是一个 L 形(阶梯形)区域,由两个左对齐的矩形拼成:
- 上矩形:
行、 列; - 下矩形:
行、 列,与上矩形共用最左边的 列。
题面用
+--+ <- 上矩形:b=2 行、a=2 列
| |
| |
+-+--+ <- 下矩形顶边,以下区域总宽 a+c = 4
| |
| | <- 下矩形:d=2 行、a+c=4 列
+----+
a c在棋盘上放
数据范围:0 0 1000 1000 1000,实现必须容忍
样例:
正解
思路
第一步:把“互不攻击”翻译成“行列都互不相同”。
第二步:先解决矩形,得到闭式
在
- 选出被占用的
行,方案数 ; - 把这
行按行序依次配上互不相同的列:第 1 行有 种选法,第 2 行剩 种…… 共 种。
两步的决策互不影响,所以
第三步:按上矩形里放几个车来分类。
L 形棋盘没法直接用矩形闭式,但它由两个矩形拼成,而上下两块的行完全不相交。 于是按“上矩形里放了多少个车”分类是天然的切入点:枚举
表示上矩形(
- 上矩形的贡献直接由闭式给出:
。 - 上矩形占用的
列,全都落在上矩形的列集 内; 而这个列集又是下矩形列集 的子集。 所以上矩形放完车后,下矩形恰好有 列不可用,可用列数变成 。 注意这里扣减的量精确等于 ,与“上矩形具体占了哪 列”无关。 - 上下两矩形的行集不相交,所以行方向不会再产生任何额外约束。
- 下矩形(
行)还要放完剩下的 个车,贡献 。
把两者相乘,对所有
为什么这样分类不重不漏。 任何一个合法摆放,它在
实现要点。
下面这张表画出
| 上矩形方案 |
下矩形可用列数 |
下矩形方案 |
该项乘积 | |
|---|---|---|---|---|
| 0 | 4 | 12 | ||
| 1 | 3 | 24 | ||
| 2 | 2 | 2 | ||
| 合计 |
上表的行就是
代码
Python 版:
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-01 00:18
# update_at: 2026-10-01 00:20
import sys
from itertools import accumulate
MOD = 100003 # 题面模数 10^5 + 3
def build_tables(limit: int) -> tuple[list[list[int]], list[list[int]]]:
"""建两张下三角表:pascal[n][r] = C(n, r),falling[n][r] = P(n, r)。
C(n, r) 用杨辉递推;P(n, r) = n(n-1)…(n-r+1) 是下降幂,
用前缀积(accumulate)一次算完整行,不必对每个 r 重新乘一遍。
表只需要前 limit+1 行——最大的下标来自 a+c、b、d、k,不超过 a+c 量级。
"""
pascal: list[list[int]] = [[1]]
for n in range(1, limit + 1):
prev = pascal[-1]
pascal.append([1] + [(prev[r - 1] + prev[r]) % MOD for r in range(1, n)] + [1])
# 前缀积的序列是 1, n, n-1, …, 1,开头补的 1 让 falling[n][0] = 1。
falling: list[list[int]] = [
list(accumulate([1, *range(n, 0, -1)], lambda x, y: x * y % MOD))
for n in range(limit + 1)
]
return pascal, falling
def solve() -> None:
a, b, c, d, k = map(int, sys.stdin.buffer.read().split())
# 组合/排列的顶层下标只会是 b、a+c、d、k 之一,取最大者即为表的规模。
pascal, falling = build_tables(max(b, a + c, d, k))
def ways(rows: int, cols: int, used: int) -> int:
"""在 rows 行 × cols 列的矩形里放 used 个互不攻击的车的方案数。
两步独立相乘:先选出被占用的 used 行,有 C(rows, used) 种;
再给这 used 行依次分配互不相同的列,有 P(cols, used) = cols(cols-1)… 种。
used 超过行数或列数时为 0(此时 C 或 P 的越界项不存在)。
"""
if used < 0 or used > rows or used > cols:
return 0
return pascal[rows][used] * falling[cols][used] % MOD
# 枚举上矩形(b 行 × a 列)里放 i 个车。
# 上矩形占用的 i 列都在它的列集内,而下矩形(d 行 × (a+c) 列)的列集
# 完整包含这个列集,所以下矩形恰好只剩 a + c - i 列可用;行互不相交,无别的耦合。
ans = sum(
ways(b, a, i) * ways(d, a + c - i, k - i) % MOD
for i in range(min(a, b, k) + 1)
)
print(ans % MOD)
if __name__ == "__main__":
solve()C++ 版(同一算法):
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-06 00:41
* update_at: 2026-10-06 00:41
*/
// L 形棋盘由两个左对齐矩形拼成:上矩形 b 行 a 列,下矩形 d 行 a+c 列(共享左侧 a 列)。
// 按上矩形里放几个车 i 分类:上矩形 C(b,i)*P(a,i),下矩形的可用列被扣成 a+c-i,
// 贡献 C(d,k-i)*P(a+c-i,k-i),对所有 i 求和即答案。
#include <cstdio>
typedef long long ll;
const ll MOD = 100003; // 题面模数 10^5 + 3
const ll MAXN = 2005; // a+c 最大 2000,所有组合/排列顶层下标都不超过它
ll n_max; // 表实际建到的最大行数
ll C[MAXN][MAXN]; // C[n][r] = 组合数 C(n, r) mod MOD,杨辉递推生成
ll P[MAXN][MAXN]; // P[n][r] = 下降幂 n(n-1)...(n-r+1) mod MOD,前缀积累乘
// 在 R 行 * Ccol 列的矩形里放 t 个互不攻击的车的方案数:
// 先选被占用的 t 行(C(R,t)),再给这 t 行依次配互不相同的列(P(Ccol,t)),两步独立。
// t 越界(不存在的方案)返回 0。
ll ways(ll R, ll Ccol, ll t) {
if (t < 0 || t > R || t > Ccol)
return 0;
return C[R][t] * P[Ccol][t] % MOD;
}
void solve() {
ll a, b, c, d, k;
scanf("%lld %lld %lld %lld %lld", &a, &b, &c, &d, &k);
// 组合/排列的顶层下标只会是 b、a+c、d、k,取最大者建表
n_max = b;
if (a + c > n_max) n_max = a + c;
if (d > n_max) n_max = d;
if (k > n_max) n_max = k;
// 杨辉递推建组合数表:C(n,0)=1,C(n,r)=C(n-1,r-1)+C(n-1,r)
for (ll n = 0; n <= n_max; ++n) {
C[n][0] = 1;
for (ll r = 1; r <= n; ++r)
C[n][r] = (C[n - 1][r - 1] + C[n - 1][r]) % MOD;
}
// 前缀积建下降幂表:P[n][0]=1,P[n][r]=P[n][r-1]*(n-r+1)
for (ll n = 0; n <= n_max; ++n) {
P[n][0] = 1;
for (ll r = 1; r <= n; ++r)
P[n][r] = P[n][r - 1] * (n - r + 1) % MOD;
}
// 枚举上矩形(b 行 a 列)里的车数 i。
// 上矩形占用的 i 列都在它的列集 {0..a-1} 内,而这个列集是下矩形列集的子集,
// 所以下矩形恰好剩 a+c-i 列可用;上下两块行集不相交,行方向没有别的耦合。
ll ans = 0;
ll i_max = a;
if (b < i_max) i_max = b;
if (k < i_max) i_max = k;
for (ll i = 0; i <= i_max; ++i) {
ll up = ways(b, a, i); // 上矩形方案数
ll down = ways(d, a + c - i, k - i); // 下矩形可用列只剩 a+c-i
ans = (ans + up * down) % MOD;
}
printf("%lld\n", ans);
}
int main() {
solve();
return 0;
}复杂度
- 时间复杂度:
。表只建到 行(不超过 量级), 杨辉递推与下降幂前缀积各 ;枚举 共 项, 每项 。与标准 C++ 正解同阶。 - 空间复杂度:
存储两张下三角表。实测最坏一组( )耗时 s、峰值内存约 MB,超过本题 MB 限制,属于允许的 Python 侧 MLE 风险; 同样算法在 C++ 里用两张 int[2001][2001]约MB,可在限制内通过。
总结
题目把“在 L 形棋盘上放
图示解析
下图给出求和的整体流程:先枚举上矩形的车数
flowchart TD
Start["读入 a b c d k"] --> Pre["建表 C(n,r) 与 P(n,r),n 到 max(b, a+c, d, k)"]
Pre --> Loop[“枚举 i = 0 .. min(a,b,k)”]
Loop --> Up[“上矩形 b×a:C(b,i)·P(a,i)”]
Loop --> Down[“下矩形 d×(a+c-i):C(d,k-i)·P(a+c-i,k-i)”]
Up --> Mul[“两项相乘并累加取模”]
Down --> Mul
Mul --> More{“i 枚举完 ?”}
More -- 否 --> Loop
More -- 是 --> Out[“输出累加和对 100003 取模”]
矩形闭式
第一步:选 2 行——只有一种,两行都占 第二步:给这 2 行配 2 个不同的列
col0 col1 row0 可选 col0 或 col1 (2 种)
row0 [X] [X] C(2,2) = 1 row1 只能选剩下那列 (1 种)
row1 [X] [X] P(2,2) = 2 * 1 = 2
合成:1 * 2 = 2 种,恰好是两条对角线摆法:
X . . X
. X X .观察重点是“选行”和“分配列”之间没有先后依赖:无论先做哪一步,结果都是

