数字三角模型
摘花生
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N][N];
int a[N][N];
int main()
{
int k;
cin >> k;
while(k--){
int n,m;
cin >> n >> m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin >> a[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
f[i][j] = max(f[i-1][j]+a[i][j],f[i][j-1]+a[i][j]);
}
}
cout << f[n][m] << endl;
}
return 0;
}
最低通行费
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 110;
int f[N][N];
int a[N][N];
int n;
//这道题可以上下左右移动,但是他要求最小值
//显然到一个点后如果再往上或者往左走这不可能会是最小值
//所以只需要考虑向右走,然后就变成那个摘花生的题目了
int main()
{
cin >> n;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin >> a[i][j];
}
}
//求最小值的时候一定要初始化!
memset(f,0x3f,sizeof f);
f[1][1] = a[1][1];
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
f[i][j] = min(f[i][j],f[i][j-1]+a[i][j]);
f[i][j] = min(f[i][j],f[i-1][j]+a[i][j]);
}
}
cout << f[n][n] << endl;
return 0;
}
方格取数
我们主要是首先假设1号和2号同时出发,每个人每次只能走一步,那么在任意时刻,1号和2号走过的路径长度是相同的所以必定有 i1 + j1 = i2 + j2
#include <iostream>
using namespace std;
const int N = 15, M = 2 * N;
int n;
int a, b, c;
int w[N][N];
int f[M][N][N];
int main()
{
//input
cin >> n;
while (cin >> a >> b >> c, a || b || c) w[a][b] += c;
//dp
for (int k = 2; k <= 2 * n; ++ k)
{
for (int i = 1; i <= n; ++ i)
{
for (int j = 1; j <= n; ++ j)
{
int &t = f[k][i][j];
//越界判断
if (k - i <= 0 || k - i > n || k - j <= 0 || k - j > n) continue;
//判断是否两条路线走到了相同的格子
int v = w[i][k - i];
if (i != j) v += w[j][k - j];//如果两条路线走到一个格子中,则只累加一次物品的价值
t = max(t, f[k - 1][i - 1][j - 1]);
t = max(t, f[k - 1][i][j - 1]);
t = max(t, f[k - 1][i - 1][j]);
t = max(t, f[k - 1][i][j]);
t += v;
}
}
}
//output
cout << f[2 * n][n][n] << endl;
return 0;
}
最长上升子序列问题
怪盗基德的滑翔翼
这个题与最长上升子序列唯一的不同就是基德可以向左右两个方向移动
所以不仅要求最长上升子序列还要求最长下降子序列
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 110;
int f[N]; //记录最长上升子序列
int f2[N]; //记录最长下降子序列
int main()
{
int k;
cin >> k;
while(k--)
{
int n;
cin >> n;
int a[N];
for(int i=1;i<=n;i++) cin >> a[i];
//求最长上升子序列
for(int i=1;i<=n;i++){
f[i] = 1;
for(int j=1;j<i;j++){
if(a[j] < a[i]) f[i] = max(f[i],f[j]+1);
}
}
//求最长下降子序列
for(int i=1;i<=n;i++){
f2[i] = 1;
for(int j=1;j<i;j++){
if(a[j] > a[i]) f2[i] = max(f2[i],f2[j]+1);
}
}
int res1 = 0;
for(int i=1;i<=n;i++) res1 = max(res1,f[i]);
int res2 = 0;
for(int i=1;i<=n;i++) res2 = max(res2,f2[i]);
cout << max(res1,res2) << endl;
}
return 0;
}
登山
即求所有这种形状的子序列
先求以 ak结尾的 从1 到 ak 的最长上升子序列
再求以 ak结尾的 从n 到 ak 的最长上升子序列即可
#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1010;
int n;
int a[N];
int f[N],g[N];
int main()
{
cin >> n;
for(int i=0;i<n;i++) cin >> a[i];
f[0] = 1;
for(int i=1;i<n;i++){
f[i] = 1;
for(int j=0;j<i;j++){
if(a[j] < a[i]) f[i] = max(f[i],f[j]+1);
}
}
g[n-1] = 1;
for(int i=n-2;i>=0;i--){
g[i] = 1;
for(int j=n-1;j>i;j--){
if(a[i] > a[j]) g[i] = max(g[i],g[j]+1);
}
}
int res = -1;
for(int i=0;i<n;i++) res = max(res,f[i]+g[i]-1);
cout << res << endl;
return 0;
}
合唱队形
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int n;
int a[N];
int f[N],g[N];
int main()
{
cin >> n;
for(int i=0;i<n;i++) cin >> a[i];
f[0] = 1;
for(int i=1;i<n;i++){
f[i] = 1;
for(int j=0;j<i;j++){
if(a[j] < a[i]) f[i] = max(f[i],f[j]+1);
}
}
g[n-1] = 1;
for(int i=n-2;i>=0;i--){
g[i] = 1;
for(int j = n - 1; j > i ; j--){
if(a[i] > a[j]) g[i] = max(g[i],g[j]+1);
}
}
int res = -1;
for(int i=0;i<n;i++) res = max(res,f[i]+g[i]-1);
cout << n - res << endl;
return 0;
}
友好城市
因变量就相当于上升子序列数组 a[N] 中的 i
自变量就相当于是 a[i] 我们要找a[N]中的一个上升子序列
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 5010;
int f[N];
pair<int,int> a[N];
int main()
{
int n;
cin >> n;
for(int i=0;i<n;i++) cin >> a[i].first >> a[i].second;
//pair中是先排first再排second
sort(a,a+n);
f[0] = 1;
for(int i=1;i<n;i++){
f[i] = 1;
for(int j=0;j<i;j++){
if(a[j].second < a[i].second) f[i] = max(f[i],f[j]+1);
}
}
int res = -1;
for(int i=0;i<n;i++) res = max(res,f[i]);
cout << res << endl;
return 0;
}
最大上升子序列和
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n;
int f[N];
int a[N];
int main()
{
cin >> n;
for(int i=1;i<=n;i++) cin >> a[i];
for(int i=1;i<=n;i++){
f[i] = a[i];
for(int j=1;j<i;j++){
if(a[j] < a[i]) f[i] = max(f[i],f[j]+a[i]);
}
}
int res = -1;
for(int i=1;i<=n;i++) res = max(res,f[i]);
cout << res << endl;
return 0;
}
最长公共子序列
最长公共子序列
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 1010;
int n,m;
char a[N],b[N];
int f[N][N];
int main()
{
cin >> n >> m >> a+1 >> b+1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
f[i][j] = max(f[i-1][j],f[i][j-1]);//这两种情况肯定有所以先写上
if(a[i]==b[j]) f[i][j] = max(f[i][j],f[i-1][j-1]+1);//这一种情况可能有
}
cout << f[n][m] << endl;
return 0;
}
最长公共上升子序列
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 3010;
int n;
int a[N],b[N];
int f[N][N];
int main()
{
cin >> n;
for(int i=1;i<=n;i++) cin >> a[i];
for(int i=1;i<=n;i++) cin >> b[i];
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
f[i][j] = f[i-1][j];
if(a[i]==b[j]){
int maxv = 1;
for(int k=1;k<j;k++){
if(b[k] < b[j]) maxv = max(maxv,f[i-1][k]+1);
}
f[i][j] = max(f[i][j],maxv);
}
}
}
int res = -1;
for(int i=1;i<=n;i++) res = max(res,f[n][i]);
cout << res << endl;
return 0;
}
最佳彩色带PAT1045
#include <iostream>
using namespace std;
const int N = 210, M = 10010;
int n, m, l;
int p[N], s[M];
int f[N][M];
int main()
{
cin >> n;
cin >> m;
for (int i = 1; i <= m; i ++ ) cin >> p[i];
cin >> l;
for (int i = 1; i <= l; i ++ ) cin >> s[i];
for (int i = 1; i <= m; i ++ )
for (int j = 1; j <= l; j ++ )
{
f[i][j] = max(f[i - 1][j], f[i][j - 1]);
if (p[i] == s[j]) f[i][j] = max(f[i][j], f[i][j - 1] + 1);
}
cout << f[m][l] << endl;
return 0;
}
背包模型
采药
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int t,m;
int f[N][N];
int the_time[N],value[N];
//f[i][j]表示前i个物品中选总时间不超过j的最大价值方案
int main()
{
cin >> t >> m;
for(int i=1;i<=m;i++) cin >> the_time[i] >> value[i];
for(int i=1;i<=m;i++){
for(int j=0;j<=t;j++){
f[i][j] = f[i-1][j];
if(j>=the_time[i]) f[i][j] = max(f[i][j],f[i-1][j-the_time[i]]+value[i]);
}
}
cout << f[m][t];
return 0;
}
装箱问题
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 40;
const int M = 20010;
int v,n;
int f[N][M],w[N];
//f[i][j]表示从前i个物品中选,总体积不超过j的最大重量方案
int main()
{
cin >> v;
cin >> n;
for(int i=1;i<=n;i++) cin >> w[i];
for(int i=1;i<=n;i++){
for(int j=0;j<=v;j++){
f[i][j] = f[i-1][j];
if(j>=w[i]) f[i][j] = max(f[i][j],f[i-1][j-w[i]]+w[i]);
}
}
cout << v - f[n][v] << endl;
return 0;
}
宠物小精灵收复
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int m,k,n;
int ball[N],blood[N];
int f[N][N];
//f[i][j][k]表示从前i个精灵中消费物品1不超过j,消耗物品2不超过k的最大方案数
int main()
{
cin >> m >> k >> n;
for(int i=1;i<=n;i++) cin >> ball[i] >> blood[i];
//目标是f[n][m][k-1],血量不能为0所以是k-1
for(int i=1;i<=n;i++){
for(int j=m;j>=ball[i];j--){
for(int t=k-1;t>=blood[i];t--){
f[j][t] = max(f[j][t], f[j - ball[i]][t - blood[i]] + 1);
}
}
}
cout << f[m][k-1] << " ";
int blood_cost;
for(int i=0; i<=k-1; i++){
if(f[m][i]==f[m][k-1]){
blood_cost = i;
break;
}
}
cout << k - blood_cost << endl;
return 0;
}
数字组合
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
const int M = 10010;
int f[N][M]; //f[i][j]表示从前i个数中选出来总值恰好等于j的最大方案数
int n,m;
int a[N];
int main()
{
cin >> n >> m;
for(int i=1;i<=n;i++) cin >> a[i];
f[0][0] = 1;
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
f[i][j] += f[i-1][j]; //不选第i个数
if(j>=a[i]) f[i][j] += f[i-1][j - a[i]];
}
}
cout << f[n][m] << endl;
return 0;
}
买书
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 1010;
int n;
int f[N][N]; //表示从前i个物品中选,价值不超过j的方案数
int a[5] = {0,10,20,50,100};
int main()
{
cin >> n;
f[0][0] = 1;
for(int i=1;i<=4;i++){
for(int j=0;j<=n;j++){
f[i][j] = f[i-1][j];
if(j >= a[i]) f[i][j] += f[i][j-a[i]];
}
}
cout << f[4][n] << endl;
return 0;
}
货币系统
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 3010;
long long a[N];
long long n,m;
long long f[N][N];
int main()
{
cin >> n >> m;
for(long long i=1;i<=n;i++) cin >> a[i];
f[0][0] = 1;
for(long long i=1;i<=n;i++){
for(long long j=0;j<=m;j++){
f[i][j] = f[i-1][j]; //这里要写等于不要写 +=
if(j >= a[i]) f[i][j] += f[i][j - a[i]];
}
}
cout << f[n][m] << endl;
return 0;
}
货币系统2
设一开始给定的货币系统是 (n,a) 也就是 a1,a2,…,an
一个与之等价的
最
优
解
货
币
系
统
(
m
,
b
)
也
就
是
b
1
,
b
2
,
.
.
.
,
b
m
最优解货币系统 (m,b) 也就是 b1,b2,...,bm
最优解货币系统(m,b)也就是b1,b2,...,bm
并且m
≤
\leq
≤n
显然有以下性质:
- 那么首先一定有
a
i
=
b
1
∗
t
1
+
b
2
∗
t
2
+
.
.
.
+
b
m
∗
t
m
ai = b1 * t1 + b2 * t2 + ... + bm * tm
ai=b1∗t1+b2∗t2+...+bm∗tm
也就是说 a [ n ] a[n] a[n]中的每一个数都能够被b[n]中的数线性组合出来 - 并且
b
1
,
b
2
,
.
.
.
,
b
m
b1,b2,...,bm
b1,b2,...,bm 一定是
a
1
,
a
2
,
.
.
.
,
a
n
a1,a2,...,an
a1,a2,...,an中的一个子集
证明: 假设b1,b2,…,bm是一个能够与a[n]等价的并且其中钞票种类最少的货币系统
设bi ∉ \notin ∈/ a[n],但是显然 b i b_i bi ∈ \in ∈ a [ n ] 所 构 成 的 数 值 空 间 a[n]所构成的数值空间 a[n]所构成的数值空间
那么就有 b i b_i bi = a 1 a_1 a1 t 1 t_1 t1 + a 2 a_2 a2 t 2 t_2 t2 + … + a n a_n an t n t_n tn
而b[n]随形成的数值空间与a[n]所形成的数值空间是等价的
那么 b i b_i bi = b 1 b_1 b1 s 1 s_1 s1 + b 2 b_2 b2 s 2 s_2 s2 + … + b n b_n bn s n s_n sn
从而bi就可以被b[n]中的其他元素线性表示
那么 b1,b2,…,bn就不是一个极大的与(n,a)等价的货币系统
综上所述: b 1 , b 2 , . . . , b m b1,b2,...,bm b1,b2,...,bm一定是 a 1 , . . . , a n a1,...,an a1,...,an的子集 - b 1 , b 2 , . . . , b m b1,b2,...,bm b1,b2,...,bm一定不能被其他bi表示出来
如
此
问
题
转
化
为
在
a
1
,
.
.
.
,
a
n
中
寻
找
一
个
极
大
无
关
组
如此问题转化为在a1,...,an中寻找一个极大无关组
如此问题转化为在a1,...,an中寻找一个极大无关组
我们先把a[n]排个序,因为大的数只可能是由小的数线性组合出来的
排完序后,我们就对每个
a
i
a_i
ai看看能否由
a
1
a_1
a1,
a
2
a_2
a2,…,
a
i
−
1
a_{i-1}
ai−1组合出来
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
//这题是一道线性代数问题
//求解一个向量组的秩(最大无关向量组的向量个数)
//但是代码写起来就是一个模拟筛的过程
//从小到大,先查看当前数有没有被晒掉,
//1)如果没有就把它加入到最大无关向量组中,并把他以及他和此前的硬币的线性组合都筛掉
//2)如果有就不理会
//即就是在完全背包求方案数的过程中,统计那些初始没有方案数的物品
//这样就变成一个完全背包问题了
const int N = 110, M = 25010;
int n;
int v[N];
bool f[M];
int main()
{
int T = 1;
cin >> T;
while (T -- )
{
cin >> n;
for (int i = 1; i <= n; ++ i) cin >> v[i];
sort(v + 1, v + n + 1);//排序的原因见之前的分析
//我们只需统计所有物品的体积是否能被其他的线性表出
//因此背包的体积只需设置为最大的物品体积即可
//res用来记录最大无关向量组的个数
int m = v[n], res = 0;
memset(f, 0, sizeof f);
f[0] = true; //状态的初值
for (int i = 1; i <= n; ++ i)
{
//如果当前物品体积被之前的物品组合线性筛掉了,则它是无效的
if (f[v[i]]) continue;
//如果没有被筛掉,则把它加入最大无关向量组
res ++ ;
//筛掉当前最大无关向量组能线性表示的体积
for (int j = v[i]; j <= m; ++ j)
{
f[j] += f[j - v[i]];
}
}
//输出最大无关向量组的向量个数
cout << res << endl;
}
return 0;
}
庆功会
#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
const int N = 510;
const int M = 6100;
int n,m;
int v[N],w[N],s[N];
int f[N][M];
int main()
{
cin >> n >> m;
for(int i=1;i<=n;i++){
cin >> v[i] >> w[i] >> s[i];
}
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
f[i][j] = f[i-1][j];
for(int k=1;k<=s[i];k++){
if(j>=k*v[i]) f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
}
}
}
cout << f[n][m] << endl;
return 0;
}
混合背包问题
朴素做法
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 1010;
int n,m;
int f[N][N];
int v[N],w[N],s[N];
int main()
{
cin >> n >> m;
for(int i=1;i<=n;i++) cin >> v[i] >> w[i] >> s[i];
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
f[i][j] = f[i-1][j];
if(s[i]==0){
for(int k=0;k*v[i]<=j;k++) f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
}
else{
for(int k=0;k<=s[i];k++){
if(j>=k*v[i]) f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
}
}
}
}
cout << f[n][m] << endl;
return 0;
}
二维费用的背包问题
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 1010;
int f[N][N];
int V[N],M[N],W[N];
int n,v,m;
int main()
{
cin >> n >> v >> m;
for(int i=1;i<=n;i++) cin >> V[i] >> M[i] >> W[i];
for(int i=1;i<=n;i++){
for(int j=v;j>=V[i];j--){
for(int k=m;k>=M[i];k--){
f[j][k] = max(f[j][k],f[j-V[i]][k-M[i]]+W[i]);
}
}
}
cout << f[v][m] << endl;
return 0;
}
潜水员
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1010;
int k; //气缸的个数
int n,m; //氧气与氮气的需要数量
int oxygen[N],nitrogen[N],weight[N];
int f[N][N];
int main()
{
cin >> n >> m;
cin >> k;
for(int i=1;i<=k;i++) cin >> oxygen[i] >> nitrogen[i] >> weight[i];
memset(f,0x3f,sizeof f);
f[0][0] = 0;
for(int i=1;i<=k;i++){
for(int a=n;a>=0;a--){
for(int b=m;b>=0;b--){//这里我们把所有j,k小于0的初始状态都合并到f[0][0][0]中来转移,也就是下面的max操作
f[a][b] = min(f[a][b],f[max(a - oxygen[i],0)][max(b - nitrogen[i],0)]+weight[i]);
}
}
}
cout << f[n][m] << endl;
return 0;
}
分组背包模板题
/*
f[i][j]表示从前i组中选物品总体积不超过j的最大重量的集合
分成选第i组的物品和不选第i组的物品两种情况
每个组里的每个物品只能选一次,需要枚举每个组的每个物品
*/
#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
const int N = 110;
int f[N][N];
int v[N][N],w[N][N];
int s[N]; //第i组的物品数量
int n,m; //物品组数和背包容量
int main()
{
cin >> n >> m;
for(int i=1;i<=n;i++){
cin >> s[i];
for(int j=1;j<=s[i];j++) cin >> v[i][j] >> w[i][j];
}
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
f[i][j] = f[i-1][j]; //不选第i组中的物品
for(int k=1;k<=s[i];k++){
if(j>=v[i][k]) f[i][j] = max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);
}
}
}
cout << f[n][m] << endl;
return 0;
}
机器分配(分组背包)
这个问题可以抽象成分组背包问题
把每个公司看做是一个物品组i,每个物品组里有M种物品,j号物品的体积是v[ i ][ j ] = j(把消耗的机器数抽象成体积)
价值是 w[ i ][ j ]
f[i][j]表示从前i组里面选总体积不超过j的最大方案
最终结果是 f[n][m]
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 20;
int v[N][N],w[N][N];
int n,m; //公司的数量以及机器台数(抽象成背包总体积)
int f[N][N];
int way[N];
int main()
{
cin >> n >> m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin >> w[i][j];
v[i][j] = j;
}
}
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
f[i][j] = f[i-1][j];
for(int k=1;k<=m;k++){
if(j>=v[i][k]) f[i][j] = max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);
}
}
}
cout << f[n][m] << endl;
//way[i]记录了i号组中选了几号物品
int j = m;
//f[i][j]表示从前i组中选总体积不超过j的方案数目
for (int i = n; i>=1; i -- ) //遍历每个分组
for (int k = 0; k <= j; k ++ )
if (f[i][j] == f[i - 1][j - v[i][k]] + w[i][k])
{
way[i] = k;
j -= v[i][k];
break;
}
for (int i = 1; i <= n; i ++ ) cout << i << ' ' << way[i] << endl;
return 0;
}
开心的金明
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 30010;
const int M = 30;
int f[M][N];
int n,m;
int v[M],p[M];//价格和重要程度
int main()
{
cin >> n >> m;
for(int i=1;i<=m;i++){
cin >> v[i] >> p[i];
}
for(int i=1;i<=m;i++){
for(int j=0;j<=n;j++){
f[i][j] = f[i-1][j];
if(j>=v[i]) f[i][j] = max(f[i][j],f[i-1][j-v[i]]+v[i]*p[i]);
}
}
cout << f[m][n] << endl;
return 0;
}
有依赖的背包问题
f[ u ][ j ]: 所有从以u为根的子树中选且总体积不超过j的方案集合
也就是说对于一个根节点为u的树,他的子树有x1,x2,…(x1,x2…均为子树的根节点)
我们为了求出根节点这个树所对应的值,进行集合划分时,不能按照传统的选法即x1选或不选,x2选或不选… …, 因为这样就会有2^k中组合出来需要遍历肯定会超时
那么现在进行一种全新的思路,对于节点u的子树,我们根据根据可以分给每个子树的体积进行搜索,如上图所示
我们可以把有依赖的背包问题看成是分组背包问题,每一个结点是看成是分组背包问题中的一个组,子节点的每一种选择我们都看作是组内的一种物品,因此我们可以通过分组背包的思想去写。
但它的难点在于如何去遍历子节点的每一种选择,即组内的物品,我们的做法是从叶子结点开始往根节点做,并使用数组表示的邻接表来存贮每个结点的父子关系。
#include <iostream>
#include <cstring>
using namespace std;
const int N = 110;
int n, m, root;
int h[N], e[N], ne[N], idx;
int v[N], w[N];
int f[N][N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u)
{
//先枚举所有状态体积小于等于j-v[u]的所有子节点们能够获得的最大价值
for (int i = h[u]; ~i; i = ne[i])
{
int son = e[i];
dfs(son); //从下往上算,先计算子节点的状态
for (int j = m - v[u]; j >= 0; -- j) //枚举所有要被更新的状态
{
for (int k = 0; k <= j; ++ k) //枚举该子节点在体积j下能使用的所有可能体积数
{
f[u][j] = max(f[u][j], f[u][j - k] + f[son][k]);
}
}
}
//最后选上第u件物品
for (int j = m; j >= v[u]; -- j) f[u][j] = f[u][j - v[u]] + w[u];
for (int j = 0; j < v[u]; ++ j) f[u][j] = 0; //清空没选上u的所有状态
}
int main()
{
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 1; i <= n; ++ i)
{
int p;
cin >> v[i] >> w[i] >> p;
if (p == -1) root = i;
else add(p, i);
}
dfs(root);
cout << f[root][m] << endl;
return 0;
}
背包问题求方案数
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1010, mod = 1e9 + 7;
int n, m;
int w[N], v[N];
int f[N][N], g[N][N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; ++ i) cin >> v[i] >> w[i];
for (int i = 1; i <= n; ++ i)
{
for (int j = 0; j <= m; ++ j)
{
f[i][j] = f[i - 1][j];
if (j >= v[i]) f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
}
g[0][0] = 1;
for (int i = 1; i <= n; ++ i)
{
for (int j = 0; j <= m; ++ j)
{
if (f[i][j] == f[i - 1][j])
g[i][j] = (g[i][j] + g[i - 1][j]) % mod;
if (j >= v[i] && f[i][j] == f[i - 1][j - v[i]] + w[i])
g[i][j] = (g[i][j] + g[i - 1][j - v[i]]) % mod;
}
}
int res = 0;
for (int j = 0; j <= m; ++ j)
{
if (f[n][j] == f[n][m])
{
res = (res + g[n][j]) % mod;
}
}
cout << res << endl;
return 0;
}