目录
开端
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]件
定义:
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;
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;
}
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;
}
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;
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;
}
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;
方法一:
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;
}
方法二:
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;
}
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]); } } }
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;
}
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];
else
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;
}
定义:每件物品需要同时花费两种不同的代价
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]);
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;
}
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]); }
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;
}
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;
}