1. 程式人生 > 資訊 >特斯拉四個月召回 11 次汽車,馬斯克稱安全機構為“掃興鬼”

特斯拉四個月召回 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分成兩類

  1. 當這j個數的最小值為1:那麼去掉一個1也同時去掉了一類數,所以f[i][j] += f[i-1][j-1]
  2. 單這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)\)

參考文章