题目描述
Level 1
题目描述
把正整数N分解成M个正整数的和,即使M个数相同但顺序不同也认为是不同的方案,要求总方案数。如3=1+2跟3=2+1是两个不同的方案。
输入
第一行包含两个整数N和M(1<=M<=N<=50)。
输出
输出一个数表示方案数。
样例输入
3 2
样例输出
2
数据范围限制
1<=M<=N<=50
Level 2
题目描述
把正整数N分解成M个非负整数的和,即使M个数相同但顺序不同也认为是不同的方案,要求总方案数。如3=1+2跟3=2+1是两个不同的方案。
输入
第一行输入两个整数(1<=M<=N<=30)。
输出
输出一个整数表示方案数。
样例输入
2 3
样例输出
6
数据范围限制
1<=M<=N<=30
Level 3
题目描述
把正整数N分解成M个正整数的和,M个加数相同但顺序不同认为是相同的方案,要求总方案数。如3=1+2跟3=2+1是两个相同的方案。
输入
第一行输入两个整数N,M(1<=M<=N<=50)。
输出
输出一个整数表示方案数。
样例输入
5 3
样例输出
2
数据范围限制
1<=M<=N<=50
分析
level 1
搜索
拿到这道题我们通常会先用搜索的角度看待本题,我们可以分层依次枚举1-(n-1),再将每一组结果用sum累加。总方案累加的条件即为sum==n且枚举个数等于m.
代码实现如下
#include<iostream>
#include<cstdio>
using namespace std;
int sum,m,n,cont;
int a[105];
void c(int q)
{
for(int i=1;i<=m;i++)
if(sum+i<=m&&q<=n)
{
a[q]=i;
sum+=a[q];
if(sum==m&&q==n){cont++;}
c(q+1);
sum-=a[q];
}
}
int main()
{
cont=0;
scanf("%d%d",&m,&n);
c(1);
printf("%d\n",cont);
}
然而。。时间复杂度已经达到了O(mn)。也就是说输入50 50时需要循环2500次!很显然,这是超时的,因此需要用其他算法改进。
递推–记忆化搜索
我们从新整理思路,从生活实际的角度开始思考。
本题非常类似放苹果,因此我们可以以1为单位,将m中的一个一看成一个盘子,n中的一个一看成一个苹果,也就是说将n个苹果放入m个盘子里,求它在考虑顺序时的不同分法。相信大家都学过,像这种题可以用夹板法求解,因此我们可以轻松求出递推公式
F[i][j]=∑i−j+1k=1F[i−k][j−1]|(i−k>=1)
F
[
i
]
[
j
]
=
∑
k
=
1
i
−
j
+
1
F
[
i
−
k
]
[
j
−
1
]
|
(
i
−
k
>=
1
)
边界条件是
F[n][1]=1,f[n][m]=1(n=m),F[0][m]=0
F
[
n
]
[
1
]
=
1
,
f
[
n
]
[
m
]
=
1
(
n
=
m
)
,
F
[
0
]
[
m
]
=
0
代码实现如下
#include<iostream>
#include<cstdio>
using namespace std;
long long f[55][55];
long long put(int n,int m)
{
if(m==1)return 1;
if(m==n)return 1;
if(n<1)return 0;
if(f[n][m])return f[n][m];
long long s=0,t=n-m+1;
for(int i=1;i<=t;i++)
s+=put(n-i,m-1);
return f[n][m]=s;
}
int main()
{
int s1,s2;
scanf("%d%d",&s1,&s2);
printf("%lld\n",put(s1,s2));
}
这么一来这道题就AC了,但是这种方法仍然不是最优的,还可以变得更简单。
递推升级–杨辉三角
仔细观察下面这个杨辉三角。
有没有发现与本题的结果有相似之处呢?例如3行2列的数字2,实际上就是F[3][2]的答案!由此,我们又可以推出一个更简化的递推公式
F[i][j]=f[i−1][j]+F[i−1][j−1]
F
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
]
+
F
[
i
−
1
]
[
j
−
1
]
边界条件只有
F[1][1]=1
F
[
1
]
[
1
]
=
1
代码实现如下
#include<iostream>
#include<cstdio>
using namespace std;
long long a[55][55],m,n;
int main()
{
int s1,s2;
scanf("%d%d",&s1,&s2);
a[1][1]=1;
for(n=2;n<=s1;n++)
for(m=1;m<=n;m++)
a[n][m]=a[n-1][m]+a[n-1][m-1];
printf("%lld\n",a[s1][s2]);
}
level 2
经过上一级的“长篇大论”,这一级就要简单许多了。
我们仍然从杨辉三角形讲起。这道题多了一个可以拆的数的存在–0。但也导致了n不一定会大于等于m。因此,这次的图不在是一个三角形了,而是一个长方形。
仍然观察本图,有没有发现与杨辉三角,与本题有很大的相似之处?
通过观察,我们会发现任意一点都等于它左边和上方两数之和。
因此,我们又可以推出递推公式
F[i][j]=F[i-1][j]+F[i][j-1]
边界是 F[1][j]=j,F[i][1]=1
代码实现如下
#include<iostream>
#include<cstdio>
using namespace std;
long long a[55][55],m,n;
int main()
{
int s1,s2;
scanf("%d%d",&s1,&s2);
for(int j=1;j<=s2;j++)a[1][j]=j;
for(int j=1;j<=s1;j++)a[j][1]=1;
for(n=2;n<=s1;n++)
for(m=2;m<=s2;m++)
a[n][m]=a[n-1][m]+a[n][m-1];
printf("%lld\n",a[s1][s2]);
}
level 3
题目又升级了!现在题目要求不考虑排列顺序了,那么,我么是否能用“图”解出来呢?
答案是肯定的,但是本题的规律复杂,数据范围小,并不如传统递归容易,如下图
与放苹果的递归相似,只需略加修改即可。
代码实现如下
#include<iostream>
#include<cstdio>
using namespace std;
int a[55][55],m,n;
int main()
{
int s1,s2;
for(n=0;n<=50;n++)
{
for(m=0;m<=50;m++)
{
if(m>n)a[n][m]=0;
else if(m==1)a[n][m]=1;
else if(n==0)a[n][m]=0;
else a[n][m]=a[n-m][m]+a[n-1][m-1];
}
}
scanf("%d%d",&s1,&s2);
printf("%d\n",a[s1][s2]);
}
总结
这三道题总的来说都是通过递推,递归完成,但都可以借助图来帮助思维简化递推公式。此外,如果有更好的方法,可以在评论区进行讨论哦