例题——放苹果
我自己读完题开始想思路时,边界条件能考虑到,并且也考虑到如果过盘子的数量大于苹果的数量,那么这种情况的放置方法总数与“盘子数量等于苹果数量”是相等的。
不过真的是没想出来最核心的状态转移方程,虽然代码通过了牛客网的在线测试,但是这个代码的复杂度比真正标准的状态转移要高,应该是有些冗余计算。
自己写的代码:
#include <iostream>
using namespace std;
int main(){
int dp[11][11];
for(int i=1;i<=10;i++)
dp[i][1]=1;
for(int j=0;j<=10;j++)
dp[0][j]=1;
for(int j=2;j<=10;j++){
for(int i=1;i<=10;i++){
if(i<j){ // 盘子的数量大于苹果的数量
dp[i][j] = dp[i][i];
}else{ // 分别考虑每个盘子最少放k个苹果的情况,然后求和
int sum=0;
for(int k=0;k<=i/j;k++){
sum+=dp[i-k*j][j-1];
}
dp[i][j] = sum;
}
}
}
int N,M;
while(cin>>M>>N){
cout<<dp[M][N]<<endl;
}
return 0;
}
基础知识——划分数问题
问题描述
- 划分数问题:有n个无区别的物品,将它们划分成不超过m组,求出划分方法数。
- 放苹果问题:把n个同样的苹果放在m个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?
解题思路——以"放苹果"为例
放苹果的问题乍看之下很复杂,盘子是一样的,苹果也是一样的;只要每个盘子里面放的苹果是一样多的,不管顺序如何最终得到的都是同一种分法。其实我们需要把问题简化。就拿这个放苹果的问题而言,我们只需要分两种情况:有空盘子和没空盘子。
- 有空盘子:f(n, m) = f(n, m-1) // 先让一个盘子空着,还有m-1个盘子,剩下的问题就是吧这n个苹果放到m-1个盘子里的问题
- 没有空盘子:f(n, m) = f(n-m, m) // 没有空盘子,我们可以看成先给每一个盘子放一个苹果,则还剩下n-m个苹果,剩下的问题就是把这n-m个苹果放到m个盘子里的问题了
因此:f(n, m) = f(n, m-1) + f(n-m, m) ( n>=m)
上面的表达式并不完整,当n<m时的情况没有考虑,当n<m的时候,f(n, m)=f(n, n)
递推一般都要有边界,稍微看一下就能找到,当只有一个盘子时明显只有一种方法,另外没有苹果和只有一个苹果的时候也只有一种放法。即当m=1或者n=0时,f(n, m) = 1
综上:
- f(n, m) = 1 (m=1,n=0)
- f(n, m) = f(n, n) (n<m)
- f(n, m) = f(n, m-1) + f(n-m, m) ( n>=m)
代码
#include <iostream>
using namespace std;
int main(){
int dp[11][11];
for(int i=1;i<=10;i++)
dp[i][1]=1;
for(int j=0;j<=10;j++)
dp[0][j]=1;
for(int j=2;j<=10;j++){
for(int i=1;i<=10;i++){
if(i<j){
dp[i][j] = dp[i][i];
}else{
dp[i][j] = dp[i][j-1]+dp[i-j][j];
}
}
}
int N,M;
while(cin>>M>>N){
cout<<dp[M][N]<<endl;
}
return 0;
}