您的位置:首页 > 其它

整数划分系列问题(动态规划)

2015-09-15 17:45 399 查看
今天上算法分析与设计课时,提到整数划分问题,但是因为之前没有很好地理解这一系列的问题。当被老师问到你们做算法的应该会这个问题吧,我当时也不太记得,但是只能硬头皮试着去写了下,用的是brute force,老师果断说我的方法跑不出正确的解,但是课堂上我也没有去解释了。课后自己验证了一下,没有问题,但是看了一下更好的动态规划的解法,顺便把几种整数划分的类型都理解清楚。

原题:一个整数划分为多个数的和,有几种划分的组合。

// 对num划分,最大的数不超过k
int divide(int num, int k) {
if(num == 0) {
return 1;
}
int res = 0;
for (int i = k; i > 0; i --) {
if(num - i >= 0) {
res += divide(num - i, i);
}
}
return res;
}
这个写法就是枚举了,效率肯定十分慢,不加记忆化的递归也快不到哪去。

加了记忆化后,n稍微大点还是跑不出来。

int d[1000][1000];
// 对num划分,最大的数不超过k
int divide(int num, int k) {
if(num == 0) {
return 1;
}
if(d[num][k] > 0) {
return d[num][k];
}
int res = 0;
for (int i = k; i > 0; i --) {
if(num - i >= 0) {
d[num-i][i] = divide(num - i, i);
res += d[num-i][i];
}
}
return res;
}


但是学会整数的划分动归求解,就可以在O(n*n)的时间复杂度内求解!!

下面进入正题。

整数划分问题变型比较多,但是有句话叫做万变不离其宗,这是看了两个贴吧大神(飞机)的讲解后领悟的,所以索性5个整数划分的问题全部列出来,自己也整理一下。

一: 将n划分成若干正整数之和的划分数。

二: 将n划分成k个正整数之和的划分数。

三: 将n划分成最大数不超过k的划分数。

四: 将n划分成若干奇正整数之和的划分数。

五: 将n划分成若干不同整数之和的划分数。

题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=571

引用一下9楼的解释,结合7楼的代码。

先做一个定义:

定义:dp(i, j)表示整数i被划分为j个数的划分数。

那么有dp(i, j) = dp(i - j, j) + dp(i - 1, j - 1)

上述划分数可以分解为两类情况:

1、分解的数含1, 即dp(i-1, j-1);

2、分解的数不含1, 那么先将分成的j个数减1,然后得到i-j,划分为j个数。即dp(i - j, j)。

问题二:将n划分成k个正整数之和的划分数 解决, 程序为:

void divide2(int n, int k) {
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= i && j <= k; j ++) {
if(j == 1)
dp[i][j] = 1;
else
dp[i][j] = dp[i-j][j] + dp[i-1][j-1];
}
}
}


问题三:将n划分成最大数不超过k的划分数。

即可以转化为:∑ dp(n, i) (1<=i<=k)

int divide3(int n, int k) {
// after divide2
int res = 0;
for (int i = 1; i <= k; i ++) {
res += dp
[i];
}
return res;
}
但是这么做就需要先求出问题二的dp数组了,有没有其他方法呢?

答案是有的

但是需要换一个思路:dp2[i][j]数组的定义为i划分最大数不超过j的划分数。

那么dp2[i][j] = dp2[i-j][j] + dp2[i][j-1]

dp2[i][j-1]表示划分数所有数都小于j的情况数。

dp2[i-j][j]表示每种情况都含至少一个最大数j, 先保留一个j,剩下的i-j进行划分,最大数仍为j。

比如dp2[9][4],先分一个4,然后dp2[5][4]还可以再分一个4出来,因此划分出来值是可以重复的。

void divide3(int n) {
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= n; j ++) {
if(i < j) {
dp2[i][j] = dp2[i][i];
}
if(i == j) {
dp2[i][j] = dp2[i][j-1] + 1;  // 1即为i自身,结合下面dp式理解。
}
if(i > j) {
// 意义不同于divide2,不要去对比。
dp2[i][j] = dp2[i-j][j] + dp2[i][j-1];
}
}
}
}


问题一:将n划分成若干正整数之和的划分数。
只要让问题3中的∑ dp(n,
i) (1<=i<=n) 或者dp2

问题五:将n划分成若干不同整数之和的划分数

上面我写了这么一句话:

“dp2[i-j][j]表示每种情况都含至少一个最大数j,
先保留一个j,剩下的i-j进行划分,最大数仍为j。
比如dp2[9][4],先分一个4,然后dp2[5][4]还可以再分一个4出来,因此划分出来值是可以重复的。”
所以要保证不重复,只需要dp2[i-j][j-1]即可。代码这一处改了就不写了。

问题四:将n划分成若干奇正整数之和的划分数。

dp3[i][j] = dp3[i-j][j] + dp[i][j-2],其中j为奇数,发现其实和问题1一样的,只是在循环控制的时候,保证j+=2就可以了,然后讨论一下几个特殊情况,看最后的代码吧。

如果类似由问题1变型为问题5也只需要将dp3[i-j][j]变为dp3[i-j][j-2]即可。

至此,五个问题均得到解决,参考贴吧7楼的代码,发现还是没有得到想要的答案还是按照自己的思路写吧。另外如果觉得循环写不好,记忆化搜索也是一种选择。

附上前面链接的题目的代码,代码写的很普通:

#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
const int maxn = 100;
int dp[maxn][maxn];     // 将i划分为最大不超过j的整数
int dp2[maxn][maxn];    // 将i划分为j个整数
int dp3[maxn][maxn];
int dp4[maxn][maxn];

int N, K;

// include or not include j
void solve13(int n) {
for (int i = 0; i <= n; i ++) {
for (int j = 1; j <= n; j ++) {
if(i < j) {
dp[i][j] = dp[i][i];
}
if(i == j) {
dp[i][j] = 1 + dp[i][j-1];
}
if(i > j) {
dp[i][j] = dp[i-j][j] + dp[i][j-1];
}
}
}
}

// include or not include 1
// 为什么上面不写循环条件 j<=i, 因为i-j < j时dp[i-j][j]不是0!!!
void solve2(int n) {
for (int i = 1; i <= n; i ++) {
for(int j = 1; j <= i; j ++) {
if(j == 1)
dp2[i][j] = 1;
else
dp2[i][j] = dp2[i-j][j] + dp2[i-1][j-1];
}
}
}

void solve4(int n) {
for (int i = 1; i <= n; i ++) {
for(int j = 1; j <= n; j += 2) {
if(i > j) {
dp3[i][j] = dp3[i-j][j] + dp3[i][j-2];
}
if(i == j) {
dp3[i][j] = dp3[i][j-2] + 1;
}
if(i < j) {
dp3[i][j] = dp3[i][i-!(i&1)];
}
}
}
}

void solve5(int n) {
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= n; j ++) {
if(i < j) {
dp4[i][j] = dp4[i][i];
}
if(i == j) {
dp4[i][j] = 1 + dp4[i][j-1];
}
if(i > j) {
dp4[i][j] = dp4[i-j][j-1] + dp4[i][j-1];
}
}
}
}
void init() {
solve13(50);
solve2(50);
solve4(50);
solve5(50);
}

int main() {
init();
int res1, res2, res3, res4, res5;
while (~scanf("%d%d", &N, &K)) {
res1 = dp

;
res2 = dp2
[K];
res3 = dp
[K];
res4 = dp3
[N-!(N&1)];
res5 = dp4

;
printf("%d\n%d\n%d\n%d\n%d\n", res1, res2, res3, res4, res5);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: