数的划分问题:
引言:将一个数n划分成m个部分和,问划分方案,划分出的各种部分的个数相等,总数相等视为一种方法,例如5=1+1+3,5=1+3+1视作一种。
第一步思路:首先要明白,如果说把n划分为m个,那么可以视作是先从n中拿一个x,题目可以变为将(n-x)分为m-1种,规模降低。
第二步思路:去重。当我们每次对这个数进行划分的时候,我们可以拿走一个x,然后将剩余部分分解为m-1种,但是如何防止不会发生重复的情况呢?
answer:我们试图保证每次拿的x'不会比上一次拿的x要小,并且每次我们只会拿一个1出来,也就是我们的x属于的范围为[1,m]。
不往上拿的原因:1.是为了保证x的非下降性,否则可能下一次拿的数的最大值都无法满足非下降性
2.将那些超过m的部分留在dp[i'][1]里,可以简化问题。
于是有递推式:dp[i][j]=dp[i-1][j-1]+dp[i-j][j]。 // dp[i][j]表示将i分解成j个部分
这样,考虑重复性就变成了如何考虑全有1的和没有1的情况。
有1的情况对应了dp[i-1][j-1],没有1的情况我们让dp[i-j][j]与之对应,理由是,我们可以把i-j看作是为j个元素预分配一个1,这样,最终的结果里不可能再有1。
或者简单来说:我们把数字i分解,那么最终的结果只可能是有1的和无1的两种情况而已。
实际上按照任意一个x分解都可以,但是用1可以保证不会越界,比如dp[5][4],如果每次都要找要2和不要2的话,很显然会出现dp[-1][1]这种数据。
把数字i分解的方案总数代码:
#include<iostream>
#include<cstring>
using namespace std;
int dp[15][15];
//可以理解为预先为每个数都分配一个1 这样就不会产生1了
int main(){
int t,v,sum;
cin>>t;
dp[0][0]=1;
dp[1][1]=1;
for(int i=1;i<=10;i++){
for(int j=1;j<=i;j++){
dp[i][j]=dp[i-1][j-1]+dp[i-j][j];
}
}
while(t--){
cin>>v;
sum=0;
for(int i=1;i<=v;i++)sum+=dp[v][i];
cout<<sum<<endl;
}
}