原题链接:
题解:
完全背包问题变形:
采用完全背包的思想求解:把1,2,3, … n分别看做n个物体的体积,这n个物体均无使用次数限制,问恰好能装满总体积为n的背包的总方案数(完全背包问题变形)
初始化:
求最大值时,当都不选时,价值显然是 0;
而求方案数时,当都不选时,方案数是 1(即前 i 个物品都不选的情况也是一种方案),所以需要初始化为 1,也即f[0][0]=1。状态计算:
这里为啥i从0开始而不是从1开始呢,f[0][0]明明是非法含义呀?背包问题里面,恰好等于j和,不超过j的状态表示有什么不同呢?
这是因为一般情况下两种方式用哪种都是可以的,可以随意选择。主要区别在于边界的初始化,和恰好是j的状态表示只有f[0][0]这个边界是合法的,其他边界状态均不合法;和不超过j的状态表示一般所有边界状态都是合法的。
状态计算:
代码:
二维:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10, mod = 1e9 + 7;
int n, f[N][N];
int main() {
cin >> n;
f[0][0] = 1;
for (int i = 1;i <= n;i++) {
for (int j = 0;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];
}
一维:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10, mod = 1e9 + 7;
int n, 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];
}
另一种DP方法:
思路:
从数量的角度出发,例如5的划分可以表示为所有总和为5,数量恰好是1~5个数的和的所有方案。
这里的状态计算(集合划分)也是一个难点。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10, mod = 1e9 + 7;
int n, f[N][N], res;
int main() {
cin >> n;
f[0][0] = 1;
for (int i = 1;i <= n;i++) {
for (int j = 1;j <= i;j++) {
f[i][j] = (f[i - 1][j - 1] + f[i - j][j]) % mod;
}
}
for (int j = 1; j <= n; j++)
res = (res + f[n][j]) % mod;
cout << res << endl;
}
dfs暴搜(时间复杂度较高无法通过,仅作为参考):
需要划分为4种情况考虑
#include<iostream>
using namespace std;
int dfs(int n, int m)
{
if (n == 1 || m == 1)//两者任意一个是1时,划分个数都是1
return 1;
else if (n < m)//例如对3进行划分成6份根本不现实,最多划分为3个1
return dfs(n, n);
else if (n == m)//例如3划分为不超过3个数的划分,其中一种必是3个1
return 1 + dfs(n, n - 1);
else
return dfs(n, m - 1) + dfs(n - m, m);
//根据划分的数中最小数是否为1,分为2种情况:
//①划分的数中最小数为1:case1(8,3)等价于case1(7,2),相当于去掉了1,个数减1,总和减1
//②划分的数中最小数大于1:case2(8,3)等价于case2(8-3,3),相当于划分的数中每个数都减去了1,因为原始值都大于1,所以总数不变,总和需要减去个数*1
}
int main(void)
{
int n;
cin >> n;
cout << dfs(n, n);//表示n的不超过n个数的划分
}