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