4002. 构造数组 (揭示了DP的本质是组合数学)
现在需要构造一对数组 ( a , b ) (a,b) (a,b),要求:
- 数组 a a a 和数组 b b b 的长度都为 m m m。
- 两个数组中的元素的取值范围都是 [ 1 , n ] [1,n] [1,n]。
- ∀ i ∈ [ 1 , m ] ∀i∈[1,m] ∀i∈[1,m], a i ≤ b i ai≤bi ai≤bi。
- 数组 a a a 中元素非严格单调递增。
- 数组 b b b 中元素非严格单调递减。
请问,共能构造出多少对满足条件的数组?
输出对 1 0 9 + 7 10^9+7 109+7 取模后的结果。
乍一看条件非常的多,感觉很难。
方法:数形结合,乘法原理(分步骤考虑)
只要在纸上随便画下 a a a 和 b b b 就能发现问题可以转化成:
- 从 n n n 个数中(每个数可以重复选)选 2 ∗ m 2*m 2∗m 个数,对这些数排列,大的一半给 b b b ,另一半给 a a a 。
- 分析 a a a 中的 m m m 个数,有多少种分配方案?
解答 2 :发现给定的 m m m 个数确定之后,方案是唯一的。
所以问题等价于 1 的方案数(经典可重集组合问题),答案是 C ( 2 m + n − 1 , n − 1 ) C(2m+n-1,n-1) C(2m+n−1,n−1)
数学推导:(思路,已知 C ( n , m ) C(n,m) C(n,m) 表示从 n n n 个不同的数中取 m m m 个的方案数)。
以下为数学中的模板思维套路:
- 设 x i x_i xi 表示第 i i i 个数取多少数。有 ∑ i = 1 n x i = 2 ∗ m , x i ≥ 0 \sum_{i=1}^{n} x_i =2*m,x_i \ge0 ∑i=1nxi=2∗m,xi≥0
- 设 x i ′ = x i + 1 x'_i = x_i +1 xi′=xi+1 有 ∑ i = 1 n x i = 2 ∗ m + n , x i > 0 \sum_{i=1}^{n} x_i =2*m+n,x_i > 0 ∑i=1nxi=2∗m+n,xi>0
- 问题转化为给定 2 ∗ m + n 2*m+n 2∗m+n 个小球,用 n − 1 n-1 n−1 个隔板将所有小球分成 n n n 部分,每部分小球的数量必须 大于 0 0 0 。因为 x i > 0 x_i>0 xi>0 ,所以隔板有 2 ∗ m + n − 1 2*m+n-1 2∗m+n−1 个位置,又因为每个隔板要不同,所以可以用 C ( 2 m + n − 1 , n − 1 ) C(2m+n-1,n-1) C(2m+n−1,n−1) 表示。得证。
一个小TRICK C ( 2 m + n − 1 , n − 1 ) C(2m+n-1,n-1) C(2m+n−1,n−1) = C ( 2 m + n − 1 , 2 m ) C(2m+n-1,2m) C(2m+n−1,2m) 哪个数据范围小用哪个
组合数怎么求? C ( n , m ) = C ( n − 1 , m − 1 ) + C ( n − 1 , m ) C(n,m) = C(n-1,m-1) + C(n-1,m) C(n,m)=C(n−1,m−1)+C(n−1,m) 这不就是 0/1背包的推导方式吗?
所以: C ( 2 m + n − 1 , n − 1 ) C(2m+n-1,n-1) C(2m+n−1,n−1) 可以用 0 / 1 0/1 0/1 背包。 2 m + n − 1 2m+n-1 2m+n−1 是背包的体积,每个元素的体积是 1 1 1 。
原来 DP 可以求方案数是因为 DP 的底层逻辑是组合数学,组合数学中的乘法原理加法原理可以求方案数。
那么一个明显的思路,可重集求方案数是不是可以直接用完全背包呢?答案是显然的!
以下给出两种背包求同一问题的代码。
0/1背包
f[0]=1;
for(int i=1;i<=2*m+n-1;i++){
for(int j=2*m;j>=1;j--){
f[j]= ((f[j] + f[j-1]) + M )%M;
}
}
cout<<f[2*m]<<endl;
完全背包
f[0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=2*m;j++){
f[j]= ((f[j] + f[j-1]) + M )%M;
}
}
cout<<f[2*m]<<endl;