整数划分问题两解

问题描述

给定一个整数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个整数之和的划分方法共有多少种。

  1. num划分为k个整数,这k个整数中有1,则除开这个1,问题的解就是将整数num-1划分为k-1个整数的解。
  2. 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;
}

猜你喜欢

转载自blog.csdn.net/conlink/article/details/79901336