题解:整数划分问题(DP)

来源:互联网 发布:君子去仁 恶乎成名 编辑:程序博客网 时间:2024/05/21 13:22

总时间限制: 200ms 内存限制: 65536kB
描述
将正整数n 表示成一系列正整数之和,n=n1+n2+…+nk, 其中n1>=n2>=…>=nk>=1 ,k>=1 。
正整数n 的这种表示称为正整数n 的划分。

输入
标准的输入包含若干组测试数据。每组测试数据是一行输入数据,包括两个整数N 和 K。
(0 < N <= 50, 0 < K <= N)

输出
对于每组测试数据,输出以下三行数据:
第一行: N划分成K个正整数之和的划分数目
第二行: N划分成若干个正整数之和的划分数目
第三行: N划分成若干个不同正整数之和的划分数目
第四行: N划分成若干个奇正整数之和的划分数目

样例输入
5 2

样例输出
2
7
3
3

分析

一 求将n划分为若干正整数之和的划分数

  1. 若划分的多个整数可以相同
      设dp[i][j]为将i划分为不大于j的划分数
      (1) 当i < j 时,i不能划分为大于i的数,所以dp[i][j]=dp[i][i];
      (2) 当i > j 时,可以根据划分中是否含有j分为两种情况。若划分中含有j,划分方案数为dp[i-j][j];若划分数中不含j,相当于将i划分为不大于j-1的划分数,为dp[i][j-1]。所以当i>j时dp[i][j]=dp[i-j][j]+dp[i][j-1];
      (3) 当i=j 时,若划分中含有j只有一种情况,若划分中不含j相当于将i划分为不大于j-1的划分数。此时dp[i][j]=1+dp[i][j-1]。
    dp[n][n]可以解决问题1,dp[n][k]表示将n划分为最大数不超过k的划分数,可以解决问题3。

  2. 若划分的正整数必须不同
      设dp[i][j]为将i划分为不超过j的不同整数的划分数
      (1) 当i < j时,i不能划分为大于i的数,所以dp[i][j]=dp[i][i];
      (2) 当i > j时,可以根据划分中是否含有j分为两种情况。若划分中含有j,则其余的划分中最大只能是j-1,方案数为dp[i-j][j-1];若划分中不含j,相当于将i划分为不大于j-1的划分数,为dp[i][j-1]。所以当i>j时dp[i][j]=dp[i-j][j-1]+dp[i][j-1];
      (3) 当i=j时,若划分中含有j只有一种情况,若划分中不含j相当于将i划分为不大于j-1的划分数。此时dp[i][j]=1+dp[i][j-1]

dp[n][n]表示将n划分为不同整数的划分数,可以解决问题5.

二 将n划分为k个整数的划分数
设dp[i][j]为将i划分为j个整数的划分数。
  (1) i < j为不可能出现的情况,dp[i][j]=0;
  (2) 若i = j,有一种情况:i可以划分为i个1之和,dp[i][j]=1;
  (3) 若i > j,可以根据划分数中是否含有1分为两类:若划分数中含有1,可以使用“截边法”将j个划分分别截去一个1,把问题转化为i-j的j-1个划分数,为dp[i-j][j-1]; 若划分中不包含1,使用“截边法”将j个划分数的最下面一个数截去,将为题转化为求i-j的j个划分数,为dp[i-j][j]。所以i>j时dp[i][j]=dp[i-j][j-1]+dp[i-j][j]。

dp[n][k]为将n划分为k个整数的划分数,可解决问题2。

三 将n划分为若干正奇数之和的划分数

设f[i][j]为将i划分为j个奇数之和的划分数,g[i][j]为将i划分为j个偶数之和的划分数。

使用截边法,将g[i][j]的j个划分都去掉1,可以得到f[i-j][j],所以

g[i][j] = f[i-j][j]。

f[i][j]中有包含1的划分方案和不包含1的划分方案。

对于包含1的划分方案,可以将1的划分除去,转化为“将i-1划分为j-1个奇数之和的划分数”,即f[i-1][j-1];对于不包含1的划分方案,可以使用截边法对j个划分每一个都去掉一个1,转化为“将i-j划分为j个偶数之和的划分数”,即g[i-j][j]。

所以f[i][j]=f[i-1][j-1]+g[i-j][j]

f[n][0]+f[n][1]+……+f[n][n]为将n划分为若干奇数的划分数,为问题4的答案。

继续上代码, 有注释

#include<cstdio>#include<cstring>#include<algorithm>#define maxn 55using namespace std;int num[maxn][maxn];//i划分为k个正整数之和int num2[maxn][maxn];//划分成若干个不同的正整数之和int f[maxn][maxn];//划分成若干个奇正整数之和int g[maxn][maxn];void init(){    int i, j;    for (int i = 1; i < maxn; i++)        num[i][0] = num[0][i] = num2[i][0] = num2[0][i] = 0;    for (int i = 1; i < maxn; i++)    {        for(int j = 1; j < maxn; j++)        {            if (i < j)                num[i][j] = 0;            else if (i == j)                num[i][j] = 1;            else//           最小划分结果是1       不含一                num[i][j] = num[i - 1][j - 1] + num[i - j][j];        }    }    f[0][0] = 1, g[0][0] = 1;    for (i = 1; i < maxn; i++)    {        for (j = 1; j <= i; j++)        {            g[i][j] = f[i - j][j];            f[i][j] = f[i - 1][j - 1] + g[i - j][j];        }    }}long long fun2(int n, int m)//计算将n进行划分, 最大的划分结果不大于m{    if (n == 1 || m == 1)        return 1;    else if (n < m)        return fun2(n, n);    else if (n == m)        return (1 + fun2(n, m - 1));    else        return (fun2(n, m - 1) + fun2(n - m, m));}int fun(int n, int m)//计算将n进行划分, 最大的划分结果小于m{    if (n == 1 && m == 1)        return 1;    else if (m == 1)        return 0;    else if (n < m)        return fun(n, n);    else if (n == m)        return (1 + fun(n, m - 1));    else        return (fun(n, m - 1) + fun(n - m, m - 1));}int main(void){    init();    int n, m, res;    while (scanf("%d %d", &n, &m) != EOF)    {        printf("%d\n", num[n][m]);        printf("%d\n", fun(n, n));          printf("%lld\n", fun2(n, n));        res = 0;        for (int i = 1; i <= n; i++)            res += f[n][i];        printf("%d\n", res);    }    return 0;}
0 0
原创粉丝点击