dp算法 - poj 4119:复杂的整数划分问题

题目

总时间限制: 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划分成若干个奇正整数之和的划分数目
样例输入
5 2
样例输出
2
3
3
提示
第一行: 4+1, 3+2,
第二行: 5,4+1,3+2
第三行: 5,1+1+3, 1+1+1+1+1+1

第一问

这个问题比之前的整数划分问题多了一个限制条件K,可以把这个问题类比为背包问题,问题转化为如下:
从[1...N]中选择K个数,其和为N,并且选数可以重复,这是个典型的动态规划问题。
那么考虑子问题,从[1...i]中选q个数,其和为j.
令f[i][j][q] 表示子问题的解。
下面考虑边界条件:从[1...1]中选择1 个数,其和为1,选法唯一 f[1][1][1] = 1;
接下来考虑状态转移:
f[i][j][q] = f[i-1][j][q] + f[i][j - i][q - 1]
f[i-1][j][q]表示第i个数不选,那么要从前i-1个数中选q个,其和为j
f[i][j - i][q-1]表示第i个数选,那么从前i个数中选q-1个,其和为j-i,这里i不取i-1是因为在子问题中还会用到i,即每个数选择不唯一,
这是区别于一般背包问题的特殊情况。
这样,解这道题使用三位数组来记录动态规划过程就可以了,但是有些动态规划是可以使用滚动数组来节省空间,这道就可以。
那么假设一个二位数组f[j][q],用一层循环表示从前i个数中选择,在第i次循环开始执行前,f[j][q]表示从前i-1个数中选q个凑成j,对于f[i][j - i][q-1]
因为在f[i][j][q]之前遍历到,所以f[j-i][q-1]中的值是第i次循环更新过的值,这样一来,状态转移就可以简化为:
f[j][q] += f[j-i][q-1]
这部分代码如下:

dp[0][0] = 1;
for (int i = 1; i <= n; i++)
    for (int j = i; j <= n; j++)
	for (int q = k; q >= 1; q--)
	    dp[j][q] += dp[j - i][q - 1];
printf("%d\n", dp[n][k]);

第二问

第二问比之前简单的整数划分问题增加了数字不能重复的限制,这表现在状态转移方程里边就是:
设f[i][j]表示从前i个数中凑j,
状态转移方程为:
f[i][j] = f[i-1][j-i] + f[i-1][j]
同样,可以用滚动数组将二维转化为一维,注意j要从大到小遍历
代码:

dp1[0] = 1;
        for (int i = 1; i <= n; i++)
            for (int j = n; j >= i; j--)
                dp1[j] += dp1[j - i];
        printf("%d\n", dp1[n]);

第三问

第三问比简单的整数划分问题增加了只能选择正奇数,注意,数字还是可以重复的,这个其实在弄懂了前两个题之后就很好写了
直接写出状态转移方程:
f[i][j] = f[i][j - i] + f[i-2][j]
f[1][1] = 1
用滚动数组的话,i取奇数遍历,j需要从小到大遍历。
代码:

dp2[0] = 1;
        for (int i = 1; i <= n; i += 2)
            for (int j = i; j <= n; j++)
                dp2[j] += dp2[j - i];
        printf("%d\n", dp2[n]);

整道题代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;

typedef long long ll;

const int N = 50 + 5;
int dp[N][N], dp1[N], dp2[N];

int main() {
    for (int n, k; scanf("%d%d", &n, &k) == 2;) {
        memset(dp, 0, sizeof(dp));
        memset(dp1, 0, sizeof(dp1));
        memset(dp2, 0, sizeof(dp2));
        dp[0][0] = 1;
        for (int i = 1; i <= n; i++)
            for (int j = i; j <= n; j++)
                for (int q = k; q >= 1; q--)
                    dp[j][q] += dp[j - i][q - 1];
        printf("%d\n", dp[n][k]);
        dp1[0] = 1;
        for (int i = 1; i <= n; i++)
            for (int j = n; j >= i; j--)
                dp1[j] += dp1[j - i];
        printf("%d\n", dp1[n]);
        dp2[0] = 1;
        for (int i = 1; i <= n; i += 2)
            for (int j = i; j <= n; j++)
                dp2[j] += dp2[j - i];
        printf("%d\n", dp2[n]);
    }
    return 0;
}

代码参考:https://blog.csdn.net/Nightmare_ak/article/details/94414363

posted @ 2020-03-24 10:48  zhangyue_lala  阅读(497)  评论(0编辑  收藏  举报