题目描述
双十一很多商品属实便宜,小明也是没控制住自己,于是这周他手机上的快递短信就爆炸了。
在兴奋的同时,如何把这些快递取回来就是一件比较麻烦的事了。
目前,小明有 m m m个快递需要取,但是他计划至多跑 n n n次就将所有的快递取回来,并且绝对不拖延的小明还要保证每次取的快递数不少于之后每次取的快递数。
但是纠结的小明就在想,一共有多少种不同的取快递方式。
不过为了减少纠结,他决定只考虑每次取的快递数,也就是假设所有快递都是完全相同的。
输入
两个正整数 m m m和 n n n,分别表示快递总数和计划最多几次取完快递。
输出
共有多少种不同的取快递的方案数(仅以每次取的快递数目做区分,即假设所有快递完全一样)
样例1输入
10 1
样例1输出
1
样例2输入
7 3
样例2输出
8
数据规模
对 20% 数据: 1 ≤ m ≤ 20 , 1 ≤ n ≤ 2 1\leq{m}\leq20, 1\leq{n}\leq2 1≤m≤20,1≤n≤2
对 60% 数据: 1 ≤ m ≤ 20 , 1 ≤ n ≤ 20 1\leq{m}\leq20, 1\leq{n}\leq20 1≤m≤20,1≤n≤20
对 80% 数据: 1 ≤ m ≤ 20 , 1 ≤ n ≤ 1 0 9 1\leq{m}\leq20, 1\leq{n}\leq{10^9} 1≤m≤20,1≤n≤109
对 100% 数据: 1 ≤ m ≤ 100 , 1 ≤ n ≤ 1 0 9 1\leq{m}\leq100, 1\leq{n}\leq{10^9} 1≤m≤100,1≤n≤109
样例解释
样例一:若10件快递至多取1次,只有1种方案,即{10}
样例二:若7件快递至多取3次,共8种不同方案,分别是:{5,1,1},{4,2,1},{3,3,1},{3,2,2},{6,1},{5,2},{4,3},{7}
解题思路及代码
这个题其实就是“同球同盒”模型,和整数拆分是一样的。
假设 n n n个相同的球,放入 m m m个相同的盒子,问有多少种放法。
假设 f ( n , m ) f(n,m) f(n,m)代表方案数。
-
当 n = 1 n=1 n=1时,很明显,只有一种方案,因为盒子是相同的,不管放入哪一个盒子都是一样的。 f ( n , m ) = 1 f(n,m)=1 f(n,m)=1。
-
当 m = 1 m=1 m=1时,很明显,也是只有一种方案,将所有的小球都放进去就行了 f ( n . m ) = 1 f(n.m)=1 f(n.m)=1。
-
当 n = 0 ∣ ∣ m = 0 n=0||m=0 n=0∣∣m=0时,表明没有球和盒子,方案数也看作为1, f ( n , m ) = 1 f(n,m)=1 f(n,m)=1。
-
当 n < m n<m n<m时,相当于 n n n个小球放在 n n n个的盒子中,即 f ( n , m ) = f ( n , n ) f(n,m)=f(n,n) f(n,m)=f(n,n)。因为最特殊的放置方法就是每个盒子中放一个球,其余的都是空的。
-
当 n = m n=m n=m时,有两种情况,
- 所有的盒子都不为空,也就是最特殊的一种方法,每个盒子中有一个球,方案数为1。
- 至少有一个盒子是空的,考虑其子问题,那么就相当于,我们去掉一个盒子,在 m − 1 m-1 m−1个盒子中,放 n n n个球。 f ( n , m − 1 ) f(n,m-1) f(n,m−1)。
所以, f ( n , m ) = 1 + f ( n , m − 1 ) f(n,m)=1+f(n,m-1) f(n,m)=1+f(n,m−1)。
-
当 n > m n>m n>m时,也是分为两种情况
- 所有的盒子都不为空,那就先从 n n n个小球中拿出 m m m个小球,分别在 m m m个盒子中各放置一个,那就相当于 n − m n-m n−m个小球放入 m m m个盒子这个子问题了。 f ( n − m , m ) f(n-m,m) f(n−m,m)。
- 至少有一个为空,考虑其子问题,那么就相当于,我们去掉一个盒子,在 m − 1 m-1 m−1个盒子中,放 n n n个球。 f ( n , m − 1 ) f(n,m-1) f(n,m−1)。
所以, f ( n , m ) = f ( n − m , m ) + f ( n , m − 1 ) f(n,m)=f(n-m,m)+f(n,m-1) f(n,m)=f(n−m,m)+f(n,m−1)。
上述是所有的情况,但是我们看到 n = m n=m n=m和 n > m n>m n>m的情况是类似的,其实是可以合并的。在 n > m n>m n>m时,当所有的盒子都不为空的时候, f ( n − m , m ) f(n-m,m) f(n−m,m),其实当 n = m n=m n=m的时候,相当于 f ( 0 , m ) = 1 f(0,m)=1 f(0,m)=1,所以是可以进行合并的。
送综上所述,结果如下:
f
(
n
,
m
)
=
{
1
n
=
1
∣
∣
m
=
1
∣
∣
n
=
0
∣
∣
m
=
0
f
(
n
,
n
)
n
<
m
f
(
n
−
m
,
m
)
+
f
(
n
,
m
−
1
)
n
≥
m
f(n,m)= \left \{ \begin{aligned} &1 \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad n=1 || m=1 || n=0 || m=0 \\ &f(n,n) \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad n<m \\ &f(n-m,m)+f(n,m-1) \quad n\geq{m} \end{aligned} \right.
f(n,m)=⎩⎪⎨⎪⎧1n=1∣∣m=1∣∣n=0∣∣m=0f(n,n)n<mf(n−m,m)+f(n,m−1)n≥m
AC代码:
#include <bits/stdc++.h>
using namespace std;
int main(){
int m,n;
//n个快递、m个盒子
cin>>n>>m;
//当n<m时,arr[n][m]=arr[n][n]
//为了节省空间,开辟到[n+1][n+1]就可以了,反正只需要算到arr[n][n]
if(n<m) m=n;
int **arr = new int*[n+1];
for(int i=0; i<n+1; i++){
arr[i] = new int[m+1];
}
//初始化
for(int i=0; i<=n; i++){
for(int j=0; j<=m; j++){
if(i==0 || i==1){
arr[i][j]=1;
}else if(j==0 || j==1){
arr[i][j]=1;
}else{
arr[i][j]=0;
}
}
}
//动态规划
for(int i=2; i<=n; i++){
for(int j=2; j<=m; j++){
if(i>=j){
arr[i][j] = arr[i-j][j]+arr[i][j-1];
}else{
arr[i][j]=arr[i][i];
}
}
}
cout<<arr[n][m]<<endl;
return 0;
}