特斯拉四個月召回 11 次汽車,馬斯克稱安全機構為“掃興鬼”
題目描述
一個正整數n可以表示成若干個正整數之和,形如:n=n1+n2+…+nk,其中n1≥n2≥…≥nk,k≥1。
我們將這樣的一種表示稱為正整數n的一種劃分。
現在給定一個正整數n,請你求出n共有多少種不同的劃分方法。
輸入格式
共一行,包含一個整數n。
輸出格式
共一行,包含一個整數,表示總劃分數量。
由於答案可能很大,輸出結果請對109+7取模。
資料範圍
1≤n≤1000
輸入樣例:
5
輸出樣例:7
演算法1:完全揹包模型
分析
可以把題目抽象成:有1,2,... ,n共n種物品,每種物品有無限多個,然後現在有一個容量也為n的揹包,問恰好把揹包裝滿有多少種不同的方案。
用f[i][j]
i
種物品,揹包容量為j
的時候,將揹包裝滿的不同方案數
那麼根據第i件物品選擇0件、1件、2件……可以得到遞推關係:f[i][j] = f[i-1][j] + f[i-1][j-i] + f[i-1][j-2*i] + f[i-1][j-3*i] + ... + f[i-1][j-k*i]
同時需要滿足 k*i <= j
程式碼
#include<iostream> #include<algorithm> #include<cstdio> using namespace std; const int N = 1010; const int mod = 1e9+7; int n; int f[N][N]; int main() { scanf("%d", &n); // 初始化 f[0][0] = 1; // for(int i = 1; i <= n; i++) // 前i件物品 { for(int j = 0; j <= n; j++) // 容量為j;一定從0開始 { for(int k = 0; k * i <= j; k++) // 第i件物品選k件 { f[i][j] = (f[i][j] + f[i-1][j-k*i]) % mod; } } } printf("%d\n", f[n][n]); return 0; }
時間複雜度
三重迴圈,\(O(n^3)\)
完全揹包模型簡化版
根據遞推方程:
f[i][j] = f[i-1][j] + f[i-1][j-i] + f[i-1][j-2*i] + f[i-1][j-3*i] + ... + f[i-1][j-k*i]
同時需要滿足 k*i <= j
f[i][j-i] = f[i-1][j-i] + f[i-1][j-2*i] + f[i-1][j-3*i] + f[i-1][j-3*i] + ... + f[i-1][j-k*i]
同時需要滿足 k*i <= j - i
所以 f[i][j] = f[i][j-i] + f[i-1][j]
所以可以將 上面程式碼簡化成如下模式
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N = 1010;
const int mod = 1e9+7;
int n;
int f[N][N];
int main()
{
scanf("%d", &n);
// 初始化
f[0][0] = 1;
//
for(int i = 1; i <= n; i++) // 前i件物品
{
for(int j = 0; j <= n; j++) // 容量為j;一定從0開始
{
f[i][j] = f[i-1][j]%mod; //
if(j >= i) f[i][j] = max(f[i][j], f[i][j-i]%mod + f[i-1][j]%mod); // 這個其實就是 f[i][j] = (f[i][j] + f[i-1][j-k*i]) % mod;的另一個表示
}
}
printf("%d\n", f[n][n]);
return 0;
}
優化為了\(O(n^2)\)
繼續優化成1維
演算法2:計數dp
分析
用f[i][j]
表示所有總和是i,恰好表示成j個數的方案總數
根據這j個數的最小值是不是1分成兩類
- 當這j個數的最小值為1:那麼去掉一個1也同時去掉了一類數,所以
f[i][j] += f[i-1][j-1]
- 單這j個數的最小值大於1:那麼每個數去掉1,總和去掉了j,數的總類數不變,所以
f[i][j] += f[i-j][j]
所以f[i][j] = f[i-1][j-1] + f[i-j][j]
程式碼
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int mod = 1e9 + 7;
const int N = 1010;
int f[N][N];
int n;
int main()
{
scanf("%d", &n);
f[0][0] = 1; // 總和是0,恰好表示成0個數的方案數為1
for(int i = 1; i <= n; i++) // 總和為i
{
for(int j = 1; j <= i; j++) // 恰好表示成j個數,其中 j <= i,總和為i,最多表示成i個數
{
f[i][j] = (f[i-1][j-1] + f[i-j][j]) % mod;
}
}
int res = 0;
for(int i = 1; i <= n; i++) res = ( res + f[n][i] ) % mod;
cout << res << endl;
return 0;
}
時間複雜度
\(O(n^2)\)