dp算法
題目
總時間限制: 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
", 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
", 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
", 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
", 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
", 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
", dp2[n]);
}
return 0;
}
代碼參考:https://blog.csdn.net/Nightmare_ak/article/details/94414363
總結
- 上一篇: 房屋维修基金是什么?房屋维修基金怎么交
- 下一篇: 炸猪皮的做法(如何炸猪皮)