AcWing 900. 整数划分

题目描述

一个正整数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)\)

参考文章

posted @ 2022-02-25 10:48  VanHope  阅读(66)  评论(0编辑  收藏  举报