目录
开端
01背包问题
AcWing 01背包问题
Luogu P2925干草出售
Luogu P1048采药
完全背包问题
AcWing 完全背包问题
Luogu P1853投资的最大效益
多重背包问题
AcWing 多重背包问题 I
AcWing 多重背包问题 II
Luogu P1776宝物筛选
混合背包问题
AcWing 混合背包问题
Luogu P1833樱花
二维费用背包问题
AcWing 二维费用的背包问题
Luogu P1507NASA的食物计划
分组背包问题
AcWing 分组背包问题
Luogu P1757 通天之分组背包
开端
关于背包问题,嗯一直学不明白,暑假咸的没事又拾起来学了一下,跟着这位大佬整理的思路(背包九讲——全篇详细理解与代码实现-CSDN博客),对背包的思想有了一定清晰的理解,大佬的文章有些长,所以跟着自己的思路再整理一下。
为了方便统一,先定义一下
c[i]:表示代价
w[i]:表示价值
dp[i][j]:表示前i个物品花费代价为j的可以获得的最大代价
p[i]:表示第i种物品最多有p[i]件
01背包问题
定义:
dp[i][j]:表示前i个物品恰放入一个容量为j的背包下可以获得的最大代价
子问题第i1件物品状态:
①不选:dp[i][j]=dp[i-1][j]②选:dp[i][j]=dp[i][j-c[i]]+w[i]
状态转移方程:
dp[i][j]=max(dp[i-1][j],dp[i][j-c[i]]+w[i])
优化空间复杂度:
O(V*N)
for(int i=1;i<=n;i++)for(int j=c[i];j<=V;j--)dp[i][j]=max(dp[i-1][j],dp[i-1][j-c[i]]+w[i]);
O(V)
for(int i=1;i<=n;i++)for(int j=V;j>=c[i];j++)dp[j]=max(dp[j],dp[j-c[i]]+w[i]);
关于顺序和逆序:
逆序表示:dp[j]=max(dp[j],dp[j-c[i]]+w[i])由dp[i][j]=max(dp[i-1][j],dp[i-1][j-c[i]]+w[i])转移过来的 顺序表示:dp[j]=max(dp[j],dp[j-c[i]]+w[i])由dp[i][j]=max(dp[i][j],dp[i][j-c[i]]+w[i])转移过来的
初始化问题:
①要求恰好装满:dp[i]=-∞,dp[0]=0;②只要求价值最大:dp[i]=0;
AcWing 01背包问题
const int N = 1010;
int c[N], w[N], dp[N];
inline void solve()
{int N, V;cin >> N >> V;for (int i = 1; i <= N; i++)cin >> c[i] >> w[i];for (int i = 1; i <= N; i++)for (int j = V; j >= c[i]; j--)dp[j] = max(dp[j], dp[j - c[i]] + w[i]);cout << dp[V] << endl;
}
Luogu P2925干草出售
const int N = 5e4 + 10;
int w[N], dp[N];
inline void solve()
{int C, H;cin >> C >> H;for (int i = 1; i <= H; i++)cin >> w[i];for (int i = 1; i <= H; i++)for (int j = C; j >= w[i]; j--)dp[j] = max(dp[j], dp[j - w[i]] + w[i]);cout << dp[C] << endl;
}
Luogu P1048采药
const int N = 1010;
int c[N], w[N], dp[N];
inline void solve()
{int T, M;cin >> T >> M;for (int i = 1; i <= M; i++)cin >> c[i] >> w[i];for (int i = 1; i <= M; i++)for (int j = T; j >= c[i]; j--)dp[j] = max(dp[j], dp[j - c[i]] + w[i]);cout << dp[T] << endl;
}
完全背包问题
定义:
dp[i][j]:表示前i种物品恰放入一个容量为j的背包下可以获得的最大代价
子问题第i种物品状态:
①不选该种物品:dp[i][j]=dp[i-1][j]; ②选不同件该种物品:选0件、1件、2件……k件:dp[i][j]=dp[i-1][j-c[i]*k]+w[i]*k;
状态转移方程:
dp[i][j]=max(dp[i-1][j-c[i]*k]+w[i]*k) 0<=c[i]*k<=j
优化空间复杂度:
O(N*∑(V/c[i]))
for(int i=1;i<=n;i++)for(int j=c[i];j<=V;j++)for(int k=0;c[i]*k<=j;k++)dp[i][j]=max(dp[i][j],dp[i-1][j-c[i]*k]+w[i]*k); # 第一个参数,因为k=0时就相当于dp[i-1][j];
O(V*N)转化为01背包问题
for(int i=1;i<=n;i++)for(int j=c[i];j<=j;j++)dp[j]=max(dp[j],dp[j-c[i]]+w[i]); //等价于dp[i][j]=max(dp[i-1][j],dp[i][j-c[i]]+w[i]);(不取该物品,取不同件);
关于顺序和逆序:
逆序表示:dp[j]=max(dp[j],dp[j-c[i]]+w[i])由dp[i][j]=max(dp[i-1][j],dp[i-1][j-c[i]]+w[i])转移过来的 顺序表示:dp[j]=max(dp[j],dp[j-c[i]]+w[i])由dp[i][j]=max(dp[i-1][j],dp[i][j-c[i]]+w[i])转移过来的
初始化问题:
①要求恰好装满:dp[i]=-∞,dp[0]=0;②只要求价值最大:dp[i]=0;
AcWing 完全背包问题
const int N = 1010;
int c[N], w[N], dp[N];
inline void solve()
{int N, V;cin >> N >> V;for (int i = 1; i <= N; i++)cin >> c[i] >> w[i];for (int i = 1; i <= N; i++)for (int j = c[i]; j <= V; j++)dp[j] = max(dp[j], dp[j - c[i]] + w[i]);cout << dp[V] << endl;
}
Luogu P1853投资的最大效益
const int N = 1e6 + 10;
int c[N], w[N], dp[N];
inline void solve()
{int s, n, d;cin >> s >> n >> d;for (int i = 1; i <= d; i++)cin >> c[i] >> w[i];while (n--){for (int i = 1; i <= d; i++)for (int j = c[i]; j <= s; j++)dp[j] = max(dp[j], dp[j - c[i]] + w[i]);s += dp[s];}cout << s << endl;
}
int main(
这个题目有个小坑
所以要做一下处理:除以1000防止爆空间
const int N = 1e6 + 10;
int c[N], w[N], dp[N];
inline void solve()
{int s, n, d;cin >> s >> n >> d;for (int i = 1; i <= d; i++)cin >> c[i] >> w[i];while (n--){for (int i = 1; i <= d; i++)for (int j = c[i] / 1000; j <= s / 1000; j++)dp[j] = max(dp[j], dp[j - c[i] / 1000] + w[i]);s += dp[s / 1000];}cout << s << endl;
}
多重背包问题
定义:
dp[i][j]:表示前i种物品恰放入一个容量为j的背包下可以获得的最大代价
子问题第i种物品状态:
①不选该种物品:dp[i][j]=dp[i-1][j]; ②选不同件该种物品:选1件、2件……p[i]件:dp[i][j]=dp[i-1][j-c[i]*k]+w[i]*k;
状态转移方程:
dp[i][j]=max(dp[i-1][j-c[i]*k]+w[i]*k) 0<=k<=p[i]
转化为01背包问题:
方法一:O(V*∑p[i])
for(int i=1;i<=n;i++)for(int j=V;j>=c[i];j--)for(int k=1;c[i]*k<=j&&k<=p[i];k++)dp[j]=max(dp[j],dp[j-c[i]*k]+w[i]*k); # 第一个参数,因为k=0时就相当于dp[i-1][j];
方法二:二进制优化O(N*log(p)*V)
for (int i = 1; i <= N; i++){int a, b, s;cin >> a >> b >> s;int k = 1;while (k <= s) //0……2^k-1部分的系数1,2,4,8……{cnt++;c[cnt] = k * a;w[cnt] = k * b;s -= k;k *= 2;}if (s > 0) //2^k……s部分的系数 s-2^k{cnt++;c[cnt] = s * a;w[cnt] = s * b;}}N = cnt; //更新总数量for (int i = 1; i <= N; i++) //01背包问题for (int j = V; j >= c[i]; j--)dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
for (int i = 1; i <= n; i++){cin >> c[i] >> w[i] >> p[i];int s = min(p[i], W / w[i]);for (int k = 1; s > 0; k <<= 1){k = min(k, s);s -= k;for (int j = W; j >= k * w[i]; j--){dp[j] = max(dp[j], dp[j - k * w[i]] + k * c[i]);}}}
初始化问题:
①要求恰好装满:dp[i]=-∞,dp[0]=0;②只要求价值最大:dp[i]=0;
方法一:
AcWing 多重背包问题 I
const int N = 110;
int c[N], w[N], p[N], dp[N];
inline void solve()
{int N, V;cin >> N >> V;int cnt = 0;for (int i = 1; i <= N; i++)cin >> c[i] >> w[i] >> p[i];for (int i = 1; i <= N; i++)for (int j = V; j >= c[i]; j--)for (int k = 1; c[i] * k <= j && k <= p[i]; k++)dp[j] = max(dp[j], dp[j - c[i] * k] + w[i] * k);cout << dp[V] << endl;
}
方法二:
AcWing 多重背包问题 II
const int N = 20010; //注意初始化,否则会越界
int c[N], w[N], dp[N];
inline void solve()
{int N, V;cin >> N >> V;int cnt = 0;for (int i = 1; i <= N; i++){int a, b, s;cin >> a >> b >> s;int k = 1;while (k <= s) //0……2^k-1部分的系数1,2,4,8……{cnt++;c[cnt] = k * a;w[cnt] = k * b;s -= k;k *= 2;}if (s > 0) //2^k……s部分的系数 s-2^k{cnt++;c[cnt] = s * a;w[cnt] = s * b;}}N = cnt; //更新总数量for (int i = 1; i <= N; i++) //01背包问题for (int j = V; j >= c[i]; j--)dp[j] = max(dp[j], dp[j - c[i]] + w[i]);cout << dp[V] << endl;
}
Luogu P1776宝物筛选
const int N = 1e6 + 10; // 注意初始化,否则会越界
int c[N], w[N], dp[N];
inline void solve()
{int n, W;cin >> n >> W;int cnt = 0;for (int i = 1; i <= n; i++){int a, b, s;cin >> a >> b >> s;int k = 1;while (k <= s){cnt++;w[cnt] = k * a;c[cnt] = k * b;s -= k;k *= 2;}if (s > 0){cnt++;w[cnt] = s * a;c[cnt] = s * b;}}n = cnt;for (int i = 1; i <= n; i++)for (int j = W; j >= c[i]; j--)dp[j] = max(dp[j], dp[j - c[i]] + w[i]);cout << dp[W] << endl;
}
简化
const int N = 1e6 + 10; // 注意初始化,否则会越界
int c[N], w[N], p[N], dp[N];
inline void solve()
{int n, W;cin >> n >> W;for (int i = 1; i <= n; i++){cin >> c[i] >> w[i] >> p[i];int s = min(p[i], W / w[i]);for (int k = 1; s > 0; k <<= 1){k = min(k, s);s -= k;for (int j = W; j >= k * w[i]; j--){dp[j] = max(dp[j], dp[j - k * w[i]] + k * c[i]);}}}cout << dp[W] << endl;
}
混合背包问题
01背包、完全背包、多重背包的混合状态转移:
for (int i = 1; i <= N; i++){cin >> c[i] >> w[i] >> p[i];// 01背包if (p[i] == -1)for (int j = V; j >= c[i]; j--)dp[j] = max(dp[j], dp[j - c[i]] + w[i]);// 完全背包else if (p[i] == 0)for (int j = c[i]; j <= V; j++)dp[j] = max(dp[j], dp[j - c[i]] + w[i]);// 多重背包二进制优化else{int s = min(p[i], V / c[i]);for (int k = 1; s > 0; k <<= 1){k = max(k, s);s -= k;for (int j = V; j >= k * c[i]; j--)dp[j] = max(dp[j], dp[j - k * c[i]] + k * w[i]);}}}
AcWing 混合背包问题
const int N = 1e6 + 10; // 注意初始化,否则会越界
int c[N], w[N], p[N], dp[N];
inline void solve()
{int N, V;cin >> N >> V;for (int i = 1; i <= N; i++){cin >> c[i] >> w[i] >> p[i];// 01背包if (p[i] == -1)for (int j = V; j >= c[i]; j--)dp[j] = max(dp[j], dp[j - c[i]] + w[i]);// 完全背包else if (p[i] == 0)for (int j = c[i]; j <= V; j++)dp[j] = max(dp[j], dp[j - c[i]] + w[i]);//或将完全背包转化为多重01背包s=V/c[i]// 多重背包二进制优化else{int s = min(p[i], V / c[i]);for (int k = 1; s > 0; k <<= 1){k = min(k, s);s -= k;for (int j = V; j >= k * c[i]; j--)dp[j] = max(dp[j], dp[j - k * c[i]] + k * w[i]);}}}cout << dp[V] << endl;
}
Luogu P1833樱花
const int N = 1e6 + 10; // 注意初始化,否则会越界
int c[N], w[N], p[N], dp[N];
inline void solve()
{int m1, m2, s1, s2, N;scanf("%d:%d %d:%d %d", &m1, &s1, &m2, &s2, &N);int V = m2 * 60 + s2 - m1 * 60 - s1;for (int i = 1; i <= N; i++){cin >> c[i] >> w[i] >> p[i];int s;if (p[i] == 0) // 完全转化为多重s = V / c[i];elses = min(p[i], V / c[i]);for (int k = 1; s > 0; k <<= 1){k = min(k, s);s -= k;for (int j = V; j >= k * c[i]; j--)dp[j] = max(dp[j], dp[j - k * c[i]] + k * w[i]);}}cout << dp[V] << endl;
}
二维费用背包问题
定义:每件物品需要同时花费两种不同的代价
dp[i][j][k]:表示前i种物品付出两种代价分别最大为j和k时可获得的最大价值
状态转移方程:
dp[i][j][k]=max(dp[i-1][j][k],dp[i-1][j-c[i]][k-m[i]]+w[i])
01背包代码(完全背包、多重背包可以类比)
for(int i=1;i<=n;i++)for(int j=V;j>=c[i];j--)for(int k=M;k>=m[i];k--)dp[j][k]=max(dp[j][k],dp[j-c[i]][k-m[i]]+w[i]);
AcWing 二维费用的背包问题
const int N = 1010; // 注意初始化,否则会越界
int c[N], w[N], m[N], dp[N][N];
inline void solve()
{int N, V, M;cin >> N >> V >> M;for (int i = 1; i <= N; i++){cin >> c[i] >> m[i] >> w[i];for (int j = V; j >= c[i]; j--)for (int k = M; k >= m[i]; k--)dp[j][k] = max(dp[j][k], dp[j - c[i]][k - m[i]] + w[i]);}cout << dp[V][M] << endl;
}
Luogu P1507NASA的食物计划
const int N = 1010; // 注意初始化,否则会越界
int c[N], w[N], m[N], dp[N][N];
inline void solve()
{int V, M, N;cin >> V >> M >> N;for (int i = 1; i <= N; i++){cin >> c[i] >> m[i] >> w[i];for (int j = V; j >= c[i]; j--)for (int k = M; k >= m[i]; k--)dp[j][k] = max(dp[j][k], dp[j - c[i]][k - m[i]] + w[i]);}cout << dp[V][M] << endl;
}
分组背包问题
定义:
dp[k][j]:表示前k组物品花费代价j能取得的最大价值
子问题第k组物品状态:
①不选该组物品:dp[k][j]=dp[k-1][j]; ②选该组物品:dp[k][j]=dp[k-1][j-c[i]+w[i]] 物品i属于k组
状态转移方程:
dp[k][j]=max(dp[k-1][j],dp[k-1][j-c[i]]+w[i])
模板:
for (int k = 1; k <= N; k++){int s;cin >> s; // 第k组的物品数量for (int i = 1; i <= s; i++)cin >> c[i] >> w[i]; // 组中每个物品i的属性for (int j = V; j >= 0; j--)for (int i = 1; i <= s; i++) // 保证每组物品只能选一个,可以覆盖之前组内物品最优解的来取最大值if (j >= c[i])dp[j] = max(dp[j], dp[j - c[i]] + w[i]);}
AcWing 分组背包问题
const int N = 110; // 注意初始化,否则会越界
int c[N], w[N], m[N], dp[N];
inline void solve()
{int N, V;cin >> N >> V;for (int k = 1; k <= N; k++){int s;cin >> s; // 第k组的物品数量for (int i = 1; i <= s; i++)cin >> c[i] >> w[i]; // 组中每个物品i的属性for (int j = V; j >= 0; j--)for (int i = 1; i <= s; i++) // 保证每组物品只能选一个,可以覆盖之前组内物品最优解的来取最大值if (j >= c[i])dp[j] = max(dp[j], dp[j - c[i]] + w[i]);}cout << dp[V] << endl;
}
Luogu P1757 通天之分组背包
const int N = 110; // 注意初始化,否则会越界
const int M = 1010; // 注意初始化,否则会越界
int c[M], w[M], dp[M];
int g[N][N], b[M]; // g[k][i]表示小组k种第i个物品的编号,b[k]表示小组k的物品+1;
inline void solve()
{int N, V;cin >> V >> N;int t = 0, k = 0;for (int i = 1; i <= N; i++){cin >> c[i] >> w[i] >> k;t = max(t, k); // 求小组的组数b[k]++; // 小组k的物品+1;g[k][b[k]] = i; // 小组k中第b[k]个物品的编号为i;}for (int k = 1; k <= t; k++)for (int j = V; j >= 0; j--)for (int i = 1; i <= b[k]; i++)if (j >= c[g[k][i]])dp[j] = max(dp[j], dp[j - c[g[k][i]]] + w[g[k][i]]);cout << dp[V] << endl;
}