算法学习笔记(46)——计数类DP

计数类 DP

题目链接:AcWing 900. 整数划分

问题描述

一个正整数 \(n\) 可以表示成若干个正整数之和,形如:\(n=n_1+n_2+\dots+n_k\),其中 \(n_1 \ge n_2 \ge \dots \ge n_k,k \ge 1\)
我们将这样的一种表示称为正整数 \(n\) 的一种划分。
现在给定一个正整数 \(n\),请你求出 \(n\) 共有多少种不同的划分方法。

输入格式

共一行,包含一个整数 \(n\)

输出格式

共一行,包含一个整数,表示总划分数量。

由于答案可能很大,输出结果请对 \(10^9+7\) 取模。

数据范围

\(1 \le n \le 1000\)

输入样例

5

输出样例

7

算法1(完全背包)

可将本题完全背包问题,容量是 \(N\) 的背包,共有\(N\) 件物品,每件物品的体积分别是 \(1 \sim N\),每种物品有无限个,求恰好装满背包的方案数。

状态表示

\(F[i,j]\) 表示从第 \(1 \sim i\) 件物品中选,恰好装满 \(j\) 体积的方案数。

阶段划分

最后一件物品的个数。

  • \(0\) 件第 \(i\) 个物品,\(F[i-1,j]\)
  • \(1\) 件第 \(i\) 个物品,\(F[i-1,j-i]\)
  • \(2\) 件第 \(i\) 个物品,\(F[i-1,j-2i]\)
  • ......(以此类推)
  • \(k\) 件第 \(i\) 个物品,\(F[i-1,j-ki],ki \le j, (k+1)i > j\)

转移方程

\[\begin{aligned} &F[i,j] = F[i-1,j] + &F[i-1,j-i] + F[i-1,j-2i] + \dots + F[i-1,j-ki] \\ &F[i,j-i] = &F[i-1][j-i] + F[i-1,j-2i] + \dots + F[i-1,j-ki] \end{aligned} \\ \]

利用完全背包的优化思路,将第二个方程代入第一个方程,得到:

\[F[i,j] = F[i-1,j] + F[i,j-i] \]

边界

容量为0时,前 i 个物品全不选也是一种方案

\[F[i,0] = 1, \text{其余为0} \]

目标

\[F[N][N] \]

#include <iostream>

using namespace std;

const int N = 1010, mod = 1e9 + 7;

int n;
int f[N][N];

int main()
{
    cin >> n;
    
    // 容量为0时,前 i 个物品全不选也是一种方案
    for (int i = 0; i <= n; i ++ ) f[i][0] = 1;
    
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ ) {
            f[i][j] = f[i - 1][j] % mod;
            if (j >= i) f[i][j] = (f[i - 1][j] + f[i][j - i]) % mod;
        }
            
    cout << f[n][n] << endl;
    
    return 0;
}

维数优化:

#include <iostream>

using namespace std;

const int N = 1010, mod = 1e9 + 7;

int n;
int f[N];

int main()
{
    cin >> n;
    
    f[0] = 1;
    
    for (int i = 1; i <= n; i ++ )
        for (int j = i; j <= n; j ++ )
            f[j] = (f[j] + f[j - i]) % mod;
            
    cout << f[n] << endl;
    
    return 0;
}

算法2

img

方案中最小值是 \(1\) 时的方案数,与去掉一个 \(1\) 的方案数 \(F[i-1,j-1]\) 相同。
方案中最小值大于 \(1\) 时的方案数,与对每一个数减 \(1\) (共有 \(j\) 个数,所以总和减 \(j\))的方案数 \(F[i-j,j]\) 相同。

转移方程

\[F[i,j] = F[i-1,j-1] + F[i-j,j] \]

边界

\[F[0,0] = 1 \]

目标

\[F[n,1] + F[n,2] + \dots + F[n,n] \]

#include <iostream>

using namespace std;

const int N = 1010, mod = 1e9 + 7;

int n;
int f[N][N];

int main()
{
    cin >> n;
    
    f[0][0] = 1;
    for (int i = 1; i <= n; i ++ )
        // 总和是i得数最多表示成i个数的和(均为1)
        for (int j = 1; j <= i; j ++ )
            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;
}
posted @ 2022-12-14 10:06  S!no  阅读(20)  评论(0编辑  收藏  举报