问题描述
给定一个整数num,问将其划分为一个或多个整数之和,可以有多少种不同的划分方法。
列如:
num=5
所有的划分方法如下:
* 5=5
* 1+4=5
* 2+3=5
* 1+1+3=5
* 1+2+2=5
* 1+1+1+2=5
* 1+1+1+1+1=5
(注意1+1+3=5和1+3+1=5视为一种划分方法)
分治法
分治思路
给定一个整数num后,可以将问题划分为几个子问题。比如num=5,我们可以先求将5划分为1个数的划分方法,再求将5划分为2个数的划分方法…最后求将5划分为5个数的划分方法。最后将这几个子问题的解加起来就可以得到我们要的答案了。
而这几个子问题又是一个同类问题,泛化的描述就是给定一个整数num,求将其划分为k个整数之和的划分方法共有多少种。
对于这个问题,我们又可以将它分为几个子问题。
要将num划分为k个整数,我们先取一个整数t,使得t<num,这个t我们从num种划分出来的第1个整数,而在保证划分不重复的情况下(如何保证不重复在代码细节处解释)能找到多少个不同的t,原问题的解就应该加多少。然后原问题就变成了求解num-t划分为k-1个整数有多少种划分方法的问题。
如此递归,便能得到问题的解。
代码解析
#pragma comment(linker, "/STACK:102400000,102400000")
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <stack>
#include <string.h>
using namespace std;
const int MAXN = 55;
int num;
stack<int> temp;
//列用栈的性质保证划分序列非递减
int solve(int num, int k) {
//返回将num分为k个数的非递减序列的分法总数
if (k == 1) return 1;
int ans = 0;
for (int i = 1; i <= num/k; i++) {
//保证划分得到的整数序列非递减
//则左边的数不大于右边的数
//i最大不能大于num/k
//否则非递减不能得到保证
if (i < temp.top()) continue;
//temp栈顶保存当前划分序列最右边(及最大)整数
//若新加入i不能保证非递减则跳过
temp.push(i);
ans += solve(num - i, k - 1);
temp.pop();
}
return ans;
}
int main(){
temp.push(0);
while (scanf("%d", &num) != EOF) {
int ans = 0;
for (int i = 1; i <= num; i++)
ans += solve(num, i);
printf("%d\n", ans);
}
return 0;
}
动态规划法
动规思路
看过分治思路后其实问题的难点已经很明显了,就是给定一个整数num,求将其划分为k个整数之和的划分方法共有多少种。
- 若num划分为k个整数,这k个整数中有1,则除开这个1,问题的解就是将整数num-1划分为k-1个整数的解。
- 若num划分为k个整数,这k个整数中没有1,则将这个k个整数各自减1后任然还有k个整数,而这时这k个整数所组成的划分方法即是问题:将整数num-k划分为k个整数的解。
采用自底向上的动规策略,已知1.和2.的解后,原问题的解就显而易见了。
状态转移方程如下:
dp[i][j] = dp[i - j][j] + dp[i - 1][j - 1];
代码解析
#pragma comment(linker, "/STACK:102400000,102400000")
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <stack>
#include <string.h>
const int MAXN = 1001;
int dp[MAXN][MAXN];
//dp[num][k] 表示将num分为k个数的划分方法数量
void dfs() {
for (int i = 1; i < MAXN; i++) {
for (int j = 2; j < i; j++) {
dp[i][j] = dp[i - j][j] + dp[i - 1][j - 1];
}
}
}
int main(){
int num, k;
memset(dp, 0, MAXN*MAXN * sizeof(int));
//初始化
for (int i = 1; i < MAXN; i++)
dp[i][1] = 1;
//将每个数划分为一个数(本身)只有一种方法
dfs();
scanf("%d%d", &num, &k);
printf("%d", dp[num][k]);
return 0;
}