算法学习笔记(46)——计数类DP
计数类 DP
问题描述
一个正整数 \(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
方案中最小值是 \(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;
}