吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 789|回复: 12
收起左侧

[Python 原创] 深挖26年广东一模压轴:动态规划、一题两解,并扩展讲解洛谷P1350!oier最喜欢的一集~

  [复制链接]
hans7 发表于 2026-4-15 23:44
本帖最后由 hans7 于 2026-4-16 08:37 编辑

引言

本文视频版传送门

视频封面:

260327-封面.png

最近广东一模的压轴题竟然考二维dp!考的还是oier耳熟能详的国际象棋棋盘放“车”的模型!这期视频我们先讲广东一模的两种解法,然后扩展到洛谷P1350的两种解法。Hans还会额外给大家送上一道类似的自编算法题喔!又是OIER最喜欢的一集,一起来领略动态规划和组合数学的魅力!

题干:

甲社区有 $n$ 个女生和 $n$ 个男生,且每个女生都认识所有男生;乙社区有 $n$ 个女生 $g_1, g_2, \cdots, g_n$$2n-1$ 个男生 $b_1, b_2, \cdots, b_{2n-1}$ ,其中女生 $g_i (i=1,2,\cdots,n)$ 认识男生 $b_j (j=1,2,\cdots,2i-1)$ ,但不认识其他男生。现从甲社区和乙社区分别选出 $m (m=1,2,\cdots,n)$ 队选手参加社区比赛,每队选手均为2人

(1)$n=3, m=1$ ,求所有参赛队伍的参赛选手性别相同的概率

(2) 若要求每队选手必须是男、女组队,且女生认识男生,分别记甲社区和乙社区选出的 $m$ 队的不同选法种数为 $A_n(m)$$B_n(m)$

  • (i)$A_n(m)$ ,并证明:当 $2 \leq m \leq n-1$ 时, $A_n(m) = A_{n-1}(m) + (2n-m)A_{n-1}(m-1)$ 三者之间的递推公式,并说明理由;(注:题干可能是命题人赶工出来的,语句不通顺。意思是让我们证明A数组满足这个递推式)

  • (ii) 若乙社区将选出的 $m$ 个男生和 $m$ 个女生按男、女搭配随机组队,求组队结果满足参赛要求的概率

本文 52pojie: https://www.52pojie.cn/thread-2103232-1-1.html

本文 博客园: https://www.cnblogs.com/hans77/articles/19874256

本文 juejin: https://juejin.cn/post/7628898648117985299

作者:hans774882968以及hans774882968以及hans774882968以及hans77

(1)送分

记事件 $A_{1},\ A_{2},\ B_{1},\ B_{2}$ 分别表示甲社区参赛选手都是女生、甲社区参赛选手都是男生、乙社区参赛选手都是女生、乙社区参赛选手都是男生。则分别考虑4种情况得:

$$ \begin{cases} P(A_{1})=P(A_{2})=\frac{C_{3}^2}{C_{6}^2}=\frac{1}{5} \\ P(B_{1})=\frac{C_{3}^2}{C_{8}^2}=\frac{3}{28} \\ P(B_{2})=\frac{C_{5}^2}{C_{8}^2}=\frac{5}{14} \end{cases} $$

所求事件概率 $P=P(A_{1})P(B_{1})+P(A_{2})P(B_{2})=\frac{13}{140}$

送分题要格外注意细节和规范

(2)证明 $A_n(m) = A_{n-1}(m) + (2n-m)A_{n-1}(m-1)$

因为甲社区是每个女生都认识所有男生,呈现一种高度对称性,所以我们可以大胆猜测:这里直接算方案数会更简单

法1:直接算反而更简单

我们考虑3步走:

  1. 找m个女生,方案数 $C_{n}^m$
  2. 找m个男生,方案数 $C_{n}^m$
  3. m男m女随机两两组队。不妨男女都从1到m编号,于是我们只需要为1到m号女生各确定一个男生编号。方案数就是排列数 $m!$

综上, $A_{n}(m)=(C_{n}^m)^2 \cdot m! = \frac{(n!)^2}{((n-m)!)^2m!}$ 。最后验证递推式就很简单了:

$$ \begin{align} rhs&=\frac{((n-1)!)^2}{((n-1-m)!)^2m!}+(2n-m)\frac{((n-1)!)^2}{((n-m)!)^2(m-1)!} \\ &=\frac{((n-1)!)^2}{((n-1-m)!)^2(m-1)!}(\frac{1}{m}+(2n-m)\frac{1}{(n-m)^2}) \\ &=\frac{((n-1)!)^2}{((n-1-m)!)^2(m-1)!} \cdot \frac{n^2}{(n-m)^2m} \\ &=\frac{(n!)^2}{((n-m)!)^2m!}=lhs \end{align} $$

法2:求递推式看似多此一举,却能给(3)启发

第二问还有一种更为复杂的做法。这里为什么要讲这种做法呢?不是多此一举吗?因为这里体现的分类讨论思想会给下一问和后面的拓展启发。

首先,我们从解读A的语义入手。 $A_{n-1}(m)$ 表示少了男生 $b_{n}$ 和女生 $g_{n}$ ,但还是有m队。 $A_{n-1}(m-1)$ 表示少了男生 $b_{n}$ 和女生 $g_{n}$ ,但少了一队。这启发我们:

考虑对男生 $b_{n}$ 和女生 $g_{n}$ 是否参加社区比赛分类讨论,共2*2=4种情况

  1. $b_{n},\ g_{n}$ 都不参加。那么其他2n-2人已经组成m队,方案数 $A_{n-1}(m)$
  2. $b_{n}$ 参加, $g_{n}$ 不参加。首先其他2n-2人组成m-1队( $A_{n-1}(m-1)$ )。此时女生还有n-m+1人没被选中,但记得排除 $g_{n}$ ,于是 $b_{n}$ 可挑选组队的女生数为 $n-m$ 。总方案数 $(n-m)A_{n-1}(m-1)$
  3. $g_{n}$ 参加, $b_{n}$ 不参加。和“2”类似,不赘述。
  4. $b_{n},\ g_{n}$ 都参加。首先其他2n-2人组成m-1队( $A_{n-1}(m-1)$ )。接下来是易错点:不要默认 $b_{n},\ g_{n}$ 在同一队。实际上 $g_{n}$ 可以选择拆散一个组好的队伍,从而和其他参赛男生组队。总方案数 $mA_{n-1}(m-1)$

综上,得递推式 $A_n(m) = A_{n-1}(m) + (2(n-m)+m)A_{n-1}(m-1) = A_{n-1}(m) + (2n-m)A_{n-1}(m-1)$

(3)直接写出通项公式,碰壁

随机组队的总方案数是 $C_{n}^mC_{2n-1}^m m!$ ,所求概率 $P=\frac{B_{n}(m)}{C_{n}^mC_{2n-1}^m m!}$ 。所以我们需要求 $B_{n}(m)$ 的通项公式。

[!note] 想法
先试试直接用组合意义写出通项公式。碰壁了就考虑子问题的性质,先求递推式再求通项!

考虑枚举被选中的女生编号 $g_{i_{1}},\ g_{i_{2}},\ \dots,\ g_{i_{m}}$ (共 $C_{n}^m$ 种情况)。不失一般性,约定 $1 \leq g_{i_{1}} \lt g_{i_{2}} \lt \dots \lt g_{i_{m}} \leq n$ 。考虑第k个被选中的女生 $g_{i_{k}}$ ,则她可选的男生数为 $(2i_{k}-1)-(k-1)=2i_{k}-k$ 。故:

$$ B_{n}(m)=\sum_{1 \leq g_{i_{1}} \lt g_{i_{2}} \lt \dots \lt g_{i_{m}} \leq n} \prod_{k=1}^m (2i_{k}-k) $$

化简这条式子似乎不容易,我们还是考虑从B的递推式入手吧!

(3)意外发现B和A是完全一样的数组!

模仿上一问的法2来分类讨论。但因为只有 $g_{n}$ 认识 $b_{2n-2},\ b_{2n-1}$ ,所以我们只按 $g_{n}$ 是否参赛来分类:

  1. $g_{n}$ 不参赛。 $B_{n-1}(m)$
  2. $g_{n}$ 参赛。首先其他3n-3人组成m-1队( $B_{n-1}(m-1)$ )。这里 $g_{n}$ 不能拆散组好的队伍,所以需要在没被选中的 $(2n-1)-(m-1)=2n-m$ 位男生中选一位组队。总方案数 $(2n-m)B_{n-1}(m-1)$

综上,得到递推式

$$ B_n(m) = B_{n-1}(m) + (2n-m)B_{n-1}(m-1) $$

诶,B的递推式刚好和A一样!

$$ A_n(m) = A_{n-1}(m) + (2n-m)A_{n-1}(m-1) $$

这说明A和B相等!

(3)收尾

不要高兴得太早!要记得检查边界情况! $A_{n}(1)=n^2,\ B_{n}(1)=1+3+\dots+(2n-1)=n^2=A_{n}(1)$ ,确实一样,放心了!

注:如果你打算法竞赛,你就会知道 $n \geq 1,\ m=0$ 可以直接约定A和B数组都为1,于是不需要验算 $B_{n}(1)$

所以所求概率

$$ P=\frac{B_{n}(m)}{C_{n}^mC_{2n-1}^m m!}=\frac{(C_{n}^m)^2 \cdot m!}{C_{n}^mC_{2n-1}^m m!}=\frac{C_{n}^m}{C_{2n-1}^m}=\frac{n!(2n-1-m)!}{(n-m)!(2n-1)!} $$

(3)回顾通项公式,看看有没有能力化简它

我们之前已经求出:

$$ B_{n}(m)=\sum_{1 \leq g_{i_{1}} \lt g_{i_{2}} \lt \dots \lt g_{i_{m}} \leq n} \prod_{k=1}^m (2i_{k}-k) $$

而刚刚我们也领悟了 $B_{n}(m)$ 的组合意义。于是我们不难想到如何拆分这个式子:把这 $C_{n}^m$ 项分为 $i_{m} \lt n$$i_{m} = n$ 两类。

  1. $i_{m} \lt n$ :就是 $B_{n-1}(m)$
  2. $i_{m} = n$ :把 $2i_{m}-m=2n-m$ 这个公因子提出来,就得到 $(2n-m)B_{n-1}(m-1)$ (懒得把式子写完整了)

如果你注意力惊人,想到通项公式递推式,就能在不知道组合意义的情况下解决这题

进一步深挖: $A_{n}(m)$ 究竟是什么?

在OEIS输入1, 64, 1568, 18816, 117600, 376320, 564480, 322560, 40320这个数列,就得到以下组合意义:

  1. T(n,k) is the number of increasing subsequences of length n-k over all permutations of [n]
  2. T(n,k) is also the number of ways to place k nonattacking rooks on an n X n chessboard

1:选择n-k个数的方案数 $C_{n}^{n-k}$ ,为n-k个数选n-k个位置的方案数 $C_{n}^{n-k}$ 。剩下k个数可以任意排,方案数 $k!$

2:引入模型1:把m个车放到 $n \times n$ 的棋盘上,要求任意两个车不能在同一行,也不能在同一列,求方案数。不难发现,前面第二问等价于模型1:有车的行可以认为是被选中的男生,有车的列可以认为是被选中的女生。

视角1:通项公式

  1. 选择m行和m列放车。方案数 $C_{n}^m \cdot C_{n}^m$
  2. 问题转化为同一问题的特殊情况:求m个车放到 $m \times m$ 的棋盘上的方案数。第一行有m种放法,第二行有m-1种,……,第m行有1种。总方案数 $m!$

综上,总方案数 $(C_{n}^m)^2 \cdot m!$

视角2:前面第二问的递推式 $A_n(m) = A_{n-1}(m) + (2n-m)A_{n-1}(m-1)$

dp[i,j]表示在i*i的棋盘上放j个车的方案数。边界条件:答案为dp[n,m]dp[i,0]=1。模仿前面第二问的法2来分类讨论

  1. dp[i-1,j]表示j个车都放在(i-1)*(i-1)的棋盘上
  2. 第i行放一个车,但第i列不放车(对应前面 $b_{i}$ 参赛 $g_{i}$ 不参赛),则 $(i,i)$ 位置不能放车。方案数 $((i-1)-(j-1))dp[i-1,j-1]=(i-j)dp[i-1,j-1]$
  3. 第i列放一个车,但第i行不放车。同上,方案数 $(i-j)dp[i-1,j-1]$
  4. 第i行第i列都放车。类似地,拆散组队的操作,对应这里挪之前放好的棋子的动作。设被挪动的棋子是 $(x,y)\ (1 \leq x,y \leq i-1)$ ,则 $g_{i}$$b_{x}$ 组队, $b_{i}$$g_{y}$ 组队,对应第j个棋子被放到 $(i,y)$ ,被挪动的棋子被放到 $(x,i)$ 。挪棋子的方案数j-1,加上第j个棋子就放在 $(i,i)$ 的1个方案,得 $jdp[i-1,j-1]$

综上,成功得到和第二问一样的递推式 $dp[i,j]=dp[i-1,j]+(2i-j)dp[i-1,j-1]$

视角3:另一种动态规划(普适性更强)

dp[i,j]表示棋盘前i行上放j个车的方案数,则:

  1. 边界条件:答案为dp[n,m]dp[i,0]=1
  2. 仍然按第j个车放的位置分类讨论
    1. dp[i-1,j]表示j个车都放在前i-1行,第i行没有车
    2. dp[i-1,j-1]表示第j个车要放在第i行。第i行有n个格子,但已经有j-1个被占用,所以有n-j+1个格子可选

所以状态转移方程:

$$ dp[i,j]=dp[i-1,j]+(n-j+1)dp[i-1,j-1] $$

相比于视角2,视角3的dp定义的普适性更强,因为它适用于任意形状的棋盘。

前面第三问等价于特定形状的棋盘放车的方案数

类似地,前面的第三问等价于这个问题:有以下形状的棋盘

x       1列
xxx     3列
...
xxxxxxx 2n-1列

把m个车放到这个棋盘上,要求任意两个车不能在同一行,也不能在同一列,求方案数。

模仿刚刚说的视角3,设dp[i,j]表示棋盘前i行放j个车的方案数,则:

  1. dp[i-1,j]表示j个车都放在前i-1行,第i行没有车
  2. dp[i-1,j-1]表示第j个车要放在第i行。第i行有 $2i-1$ 个格子,但已经有j-1个被占用,所以有 $(2i-1)-(j-1)=2i-j$ 个格子可选

所以状态转移方程:

$$ dp[i,j]=dp[i-1,j]+(2i-j)dp[i-1,j-1] $$

前面第三问之所以能求出和第二问一样的递推式,是因为第二问采用了视角2的dp定义,第三问采用了视角3的dp定义!妙哉!

扩展2:我自编的一道水题

我们发现视角3介绍的dp对于任意形状的棋盘都适用。所以不妨来做做下面这题:棋盘有2n行,第2k+1、2k+2行(k从0到n-1)的格子数是3k+2(每一行的格子数:2、2、5、5、8、8……)。试求放m个车的方案数

只需要把之前视角3的状态转移方程的系数,改成代码里的get_width函数

def compute_dp(n, m_max=None):
    """
    计算升级版棋盘的dp[i][j]
    dp[i][j] = 前i行放置j个非进攻型车的方案数
    第2k+1行和第2k+2行各有3k+2个可放置列
    """
    rows = 2 * n
    if m_max is None:
        m_max = rows
    dp = [[0] * (m_max + 1) for _ in range(rows + 1)]
    for i in range(rows + 1):
        dp[i][0] = 1

    def get_width(row):
        """第row行的可放置列数(row从1开始)"""
        k = (row - 1) // 2
        return 3 * k + 2

    for i in range(1, rows + 1):
        width_i = get_width(i)
        for j in range(1, min(i, m_max) + 1):
            # 递推式:
            # 1. 第i行不放车:dp[i-1][j]
            # 2. 第i行放车:(width_i - (j-1)) * dp[i-1][j-1]
            if width_i >= j:
                dp[i][j] = dp[i - 1][j] + (width_i - j + 1) * dp[i - 1][j - 1]
            else:
                dp[i][j] = dp[i - 1][j]
    return dp

扩展3:洛谷P1350

洛谷P1350题干传送门

有这样一个网格棋盘,$a,b,c,d$ 表示对应边长度,也就是对应的格子数:

洛谷1350-1.png

$a=b=c=d=2$ 时,对应这样一个棋盘:

洛谷1350-2.png

要在这个棋盘上放 $k$ 个相互不攻击的车,也就是这 $k$ 个车没有两个车在同一行,也没有两个车在同一列,问有多少种方案。

输入五个非负整数 $0\leq a,b,c,d,k\leq 10^3$ ,且至少有一种可行方案。答案要 $\bmod$ $10^5+3$

法1:扩展模型1求通项公式

首先,我们把模型1扩展到n*m棋盘的情况(模型2):把k个车放到 $n \times m$ 的棋盘上,要求任意两个车不能在同一行,也不能在同一列,求方案数

同模型1,方案数是 $C_{n}^k \cdot C_{m}^k \cdot k!$$C_{n}^k \cdot C_{m}^k$ 表示把问题转化为求k个车放到 $k \times k$ 的棋盘上的方案数

棋盘可以看成由两个矩形组成,所以我们可以有两个想法:

  1. 先在c*d的矩形上放车,再在a*(b+d)的矩形上放车
  2. 先在a*b的矩形上放车,再在(a+c)*d的矩形上放车

1,设在c*d的矩形上放了i个车( $0 \leq i \leq k$ )。因为a*(b+d)的矩形有i行被占用了,所以相当于矩形变成了a*(b+d-i)。套模型2的公式得总方案数:

$$ \sum_{i=0}^{k} C_{c}^iC_{d}^ii! \cdot C_{a}^{k-i}C_{b+d-i}^{k-i}(k-i)! $$

2,设在a*b的矩形上放了i个车( $0 \leq i \leq k$ )。因为(a+c)*d的矩形有i列被占用了,所以相当于矩形变成了(a+c-i)*d。套模型2的公式得总方案数:

$$ \sum_{i=0}^{k} C_{a}^iC_{b}^ii! \cdot C_{d}^{k-i}C_{a+c-i}^{k-i}(k-i)! $$
MOD = 100003
MAX_N = 2005

C = [[0] * MAX_N for _ in range(MAX_N)]
fact = [1] * MAX_N

def precompute():
    for i in range(MAX_N):
        C[i][0] = 1
        for j in range(1, i + 1):
            C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD
    for i in range(1, MAX_N):
        fact[i] = (fact[i - 1] * i) % MOD

def solve_expr1(a, b, c, d, k):
    """
    计算表达式 1:
    sum_{i=0}^{k} C(c, i) * C(d, i) * i! * C(a, k-i) * C(b+d-i, k-i) * (k-i)!
    """
    ans = 0
    for i in range(k + 1):
        term = C[c][i] * C[d][i] % MOD * fact[i] % MOD * C[a][k - i] % MOD
        n2 = b + d - i
        m2 = k - i
        if n2 >= 0:
            term = (term * C[n2][m2]) % MOD
        else:
            term = 0
        term = (term * fact[k - i]) % MOD
        ans = (ans + term) % MOD
    return ans

if __name__ == "__main__":
    precompute()
    a, b, c, d, k = list(map(int, input().split()))
    ans = solve_expr1(a, b, c, d, k)
    print(ans)

法2:动态规划

前面介绍的视角3的dp适用于任何形状的棋盘,那自然也适用于这题。设dp[i,j]表示棋盘前i行上放j个车的方案数,则:

$$ dp[i,j]=dp[i-1,j]+K \cdot dp[i-1,j-1] $$

其中K是一个跟行号有关的函数 $K(i)$K(i)=(a - j + 1) if i <= b else (a + c - j + 1)

另外,为了减少代码的运行时间,记得约束j(枚举放置的车的数目)的范围:j_lim = min(k, a) if i <= b else min(k, a + c)

MOD = 100003
MAX_N = 2005
a, b, c, d, k = list(map(int, input().split()))
dp = [[0] * MAX_N for _ in range(MAX_N)]
dp[0][0] = 1
for i in range(1, b + d + 1):
    j_lim = min(k, a) if i <= b else min(k, a + c)
    for j in range(j_lim + 1):
        coeff = (a - j + 1) if i <= b else (a + c - j + 1)
        dp[i][j] = (dp[i - 1][j] + (dp[i - 1][j - 1] if j > 0 else 0) * coeff % MOD) % MOD
print(dp[b + d][k])

免费评分

参与人数 3吾爱币 +2 热心值 +3 收起 理由
Lyss07 + 1 + 1 热心回复!
烧饼馒头包子 + 1 就是厉害
ioyr5995 + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| hans7 发表于 2026-4-16 08:40
52soft 发表于 2026-4-16 08:16
有部分公式没有正常显示

刚刚排查了一下,原来是52pojie的markdown解析器不支持公式直接输入小于号,而其他平台支持!已修改完毕~
嘿嘿嘿001 发表于 2026-4-16 00:00
bobian 发表于 2026-4-16 00:19
Kristia 发表于 2026-4-16 00:36
大神就是厉害
chenrdong 发表于 2026-4-16 02:05
厉害了,虽然看不懂。。。
52soft 发表于 2026-4-16 08:16
有部分公式没有正常显示
Mzhang2008 发表于 2026-4-16 08:21
技术人才,很强大
xilou 发表于 2026-4-16 08:32
说实话,这题目就不是给我看的
yvyvsi 发表于 2026-4-16 09:58
不愧是大神,学习到了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-4-17 05:18

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表