4002 构造数组(可重复组合数问题--隔板法)

1. 问题描述:

现在需要构造一对数组 (a,b),要求:
数组 a 和数组 b 的长度都为 m。
两个数组中的元素的取值范围都是 [1,n]。
∀i∈[1,m],ai ≤ bi。
数组 a 中元素非严格单调递增。
数组 b 中元素非严格单调递减。
请问,共能构造出多少对满足条件的数组?输出对 10 ^ 9 + 7 取模后的结果。

输入格式

一行,两个整数 n,m。

输出格式

一个整数,表示满足条件的数组对的数量对 10 ^ 9 + 7 取模后的结果。

数据范围

前三个测试点满足,1 ≤ n,m ≤ 10。
所有测试点满足,1 ≤ n ≤ 1000,1 ≤ m ≤ 10。

输入样例:

2 2

输出样例:

5
来源:https://www.acwing.com/problem/content/description/4005/

2. 思路分析:

首先我们需要对题目做一些转换,根据题目的描述我们将其分为两步来构造方案:

  1. 先从1~n中选出来2m个数字
  2. 然后对1中选出来的数进行排列使得满足题目的要求

对于每一种选出来的排列重新排列满足题目要求对应的方案数目累加就是答案,这里可以发现2. 中重新排列之后前m个元素一定a中的元素,后m个元素一定是b中的元素,b中的元素大于等于a中的所有元素值,所以对于1. 中选出来的2m个数字最终的排列一定是唯一确定的,所以方案数目为1,所以总的方案数目取决于1. 中的排列数目,相当于是在1~n中选出来的2m个数字,并且选出来的数字是可以重复的(可重复的组合计数问题),可以做以下的转化,转化1:设1~n中每一个数选择了xi次,其中xi >= 0,xi = 0表示不选数字xi,因为最终需要满足选择的数目为2m个,所以可以得到下面的不定方程:

  • x1 + x2 + ... + xn = 2m,xi >= 0

转化完之后可以发现不定方程的解与原问题的解是等价的,任何一个不定方程的解都对应原问题的一种选法,反过来也是成立的任何一种选法都对应不定方程的一个解;求解n元线性不定方程属于一个经典的数学问题,对于这种xi >= 0的限制的不定方程有一个经典的套路,也需要将其转化一下,将xi >= 0转化为xi’ = xi + 1 >= 1,也即转化2:

  • x1' + x2' + ... + xn' = 2m + n,其中xi' = xi + 1,xi >= 1

转化完之后可以发现两个方程的解也是等价的,对于xi >= 1的线性不定方程我们可以使用隔板法来解决,可以看成是2m + n个小球,n - 1个隔板,n - 1个板子可以将所有的球分成n部分,每一种放隔板的方法都对应不定方程的解,反过来也是成立的任何一组不定方程的解都对应一种放法,所以是完全等价的,所以最终求解的是2m + n - 1个空位放n - 1个板子的方案数目,也即转化为了组合数的问题,因为数据范围不是特别大,所以可以使用递推的方法求解组合数,最终的答案为C2m + n - 1 n - 1 = C2m + n- 1 2m,因为m远小于n所以使用第二个公式求解,递推的时候使用公式Ca b = Ca-1 b + Ca-1 b-1递推即可。

3. 代码如下:

class Solution:
    def process(self):
        N, M, mod = 2000, 30, 10 ** 9 + 7
        C = [[0] * M for i in range(N)]
        n, m = map(int, input().split())
        a, b = 2 * m + n - 1, 2 * m
        for i in range(a + 1):
            j = 0
            # 因为上界是b而且b远小于a所以可以加上一个j <= b的限制
            while j <= i and j <= b:
                # j = 0的时候方案数目为1
                if j == 0:
                    C[i][j] = 1
                else:
                    C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod
                j += 1
        return C[a][b]


if __name__ == '__main__':
    print(Solution().process())

你可能感兴趣的:(acwing-提高,算法)