大佬的博客!!!推荐!!!
1. Steadily Growing Steam
题意:
有
N
N
N个物品,每个物品的体积为
t
i
t_i
ti,价值为
v
i
v_i
vi。在选择物品之前,你可以选择
0
∼
k
0 \sim k
0∼k个物品的体积翻倍,然后需要在这
N
N
N个物品中选出若干个物品分成
A
A
A,
B
B
B两堆,使得这
A
A
A和
B
B
B的体积相同,并且价值之和最大。求这个价值的最大值。
1
≤
n
≤
100
,
t
i
≤
13
,
0
≤
k
≤
n
,
∣
v
i
∣
≤
1
0
9
1 \le n \le 100,t_i \le 13,0 \le k \le n, |v_i| \le 10^9
1≤n≤100,ti≤13,0≤k≤n,∣vi∣≤109
solution:
01背包稍微变化:
背包的体积为设为
N
∗
26
N * 26
N∗26
如果放入
A
A
A堆里,体积为负数;如果放入
B
B
B堆里,体积为正数。
答案就是求体积为
N
∗
13
N * 13
N∗13的背包的最大价值
- 状态定义: f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]:表示选了前 i i i个物品,有 j j j个物品体积翻倍,背包体积为 k k k的所有方案价值的最大值
- 状态计算:对于第
i
i
i个物品:
1.不放, f [ i ] [ j ] [ k ] = f [ i − 1 ] [ j ] [ k ] f[i][j][k] = f[i - 1][j][k] f[i][j][k]=f[i−1][j][k]
2.放入 A A A, f [ i ] [ j ] [ k ] = m a x ( f [ i ] [ j ] [ k ] , f [ i − 1 ] [ j ] [ k − v i ] + w i ) f[i][j][k] = max(f[i][j][k], f[i - 1][j][k - v_i] + w_i) f[i][j][k]=max(f[i][j][k],f[i−1][j][k−vi]+wi)
3.放入 B B B, f [ i ] [ j ] [ k ] = m a x ( f [ i ] [ j ] [ k ] , f [ i − 1 ] [ j ] [ k + v i ] + w i ) f[i][j][k] = max(f[i][j][k], f[i - 1][j][k + v_i] + w_i) f[i][j][k]=max(f[i][j][k],f[i−1][j][k+vi]+wi)
4.翻倍放入 A A A, f [ i ] [ j ] [ k ] = m a x ( f [ i ] [ j ] [ k ] , f [ i − 1 ] [ j − 1 ] [ k − 2 v i ] + w i ) f[i][j][k] = max(f[i][j][k], f[i - 1][j - 1][k -2 v_i] + w_i) f[i][j][k]=max(f[i][j][k],f[i−1][j−1][k−2vi]+wi)
5.翻倍放入 B B B, f [ i ] [ j ] [ k ] = m a x ( f [ i ] [ j ] [ k ] , f [ i − 1 ] [ j − 1 ] [ k + 2 v i ] + w i ) f[i][j][k] = max(f[i][j][k], f[i - 1][j - 1][k + 2v_i] + w_i) f[i][j][k]=max(f[i][j][k],f[i−1][j−1][k+2vi]+wi)
另外,可以利用滚动数组进行优化空间,但好像此题空间刚刚好,不优化也行
Code:
#include <bits/stdc++.h>
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define x first
#define y second
using namespace std;
const int N = 110, M = 50 * 110;
pair<int, int> a[N];
int f[N][N][M];
int n, m;
void Max(int &x, int y) {
if(x < y) x = y;
}
signed main()
{
cin >> n >> m;
for(int i = 1; i <= n; ++i) cin >> a[i].x >> a[i].y;
memset(f, -0x3f, sizeof f);
f[0][0][n * 13] = 0;
for(int i = 1; i <= n; ++ i) {
for(int j = 0; j <= m; ++ j) {
for(int k = 0; k <= n * 26; ++ k) {
Max(f[i][j][k], f[i - 1][j][k]);
if(k - a[i].y>= 0)
Max(f[i][j][k], f[i - 1][j][k - a[i].y] + a[i].x);
if(k + a[i].y <= n * 26)
Max(f[i][j][k], f[i - 1][j][k + a[i].y] + a[i].x);
if(j > 0) {
if(k - 2 * a[i].y >= 0)
Max(f[i][j][k], f[i - 1][j - 1][k - 2 * a[i].y] + a[i].x);
if(k + 2 * a[i].y <= n * 26)
Max(f[i][j][k], f[i - 1][j - 1][k + 2 * a[i].y] + a[i].x);
}
}
}
}
int res = 0;
for(int i = 0; i <= m; ++ i) res = max(res, f[n][i][n * 13]);
cout << res << "\n";
}
2. 选数
题意:
有
n
n
n个数,现在需要选出
k
k
k个。让这
k
k
k个数相乘后末尾零的个数最多
solution:
考虑背包dp:
- 状态表示:
d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示前 i i i个物品,选了 j j j个,且 5 5 5的因子数量为 k k k的所有方案中因子 2 2 2的最大值 - 状态计算:
不选第 i i i个: d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j ] [ k ] dp[i][j][k] = dp[i - 1][j][k] dp[i][j][k]=dp[i−1][j][k]
选第 i i i个:可以看作一个体积为 c n t 5 cnt_5 cnt5,价值为 c n t 2 cnt_2 cnt2的物品放入背包。也就是: d p [ i ] [ j ] [ k ] = m a x ( d p [ i ] [ j ] [ k ] , d p [ i − 1 ] [ j − 1 ] [ k − c n t 5 [ i ] ] + c n t 2 [ i ] ) dp[i][j][k] = max(dp[i][j][k], dp[i - 1][j - 1][k - cnt_5[i]] + cnt_2[i]) dp[i][j][k]=max(dp[i][j][k],dp[i−1][j−1][k−cnt5[i]]+cnt2[i])
那么根据dp的定义,最后的结果就是 1 ≤ m ≤ c n t , m i n ( d p [ n ] [ k ] [ m ] , m ) 1 \le m \le cnt, \quad min(dp[n][k][m], m) 1≤m≤cnt,min(dp[n][k][m],m)
然后对一维进行滚动数组优化
预处理一下第
i
i
i个数
2
,
5
2,5
2,5的因子个数
Code:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 210, M = 6010;
int dp[2][N][M];
int cnt_2[N], cnt_5[N], cnt;
int n, m;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; ++ i) {
int x; cin >> x;
while(x % 2 == 0) x /= 2, cnt_2[i] ++;
while(x % 5 == 0) x /= 5, cnt_5[i] ++;
cnt += cnt_5[i];
}
memset(dp, -1, sizeof dp);
dp[0][0][0] = dp[1][0][0] = 0;
for(int i = 1; i <= n; ++ i) {
for(int j = 1; j <= m; ++ j) {
for(int k = cnt; k >= 0; -- k)
dp[i & 1][j][k] = dp[i - 1 & 1][j][k];
for(int k = cnt; k >= cnt_5[i]; -- k)
if(dp[i - 1 & 1][j - 1][k - cnt_5[i]] >= 0)
dp[i & 1][j][k] = max(dp[i & 1][j][k], dp[i - 1 & 1][j - 1][k - cnt_5[i]] + cnt_2[i]);
}
}
int res = 0;
for(int i = 0; i <= cnt; ++ i)
res = max(res, min(dp[n & 1][m][i], i));
cout << res << "\n";
}
3. 宠物小精灵之收服
题意:
小智有
N
N
N个精灵球,皮卡丘有
M
M
M的体力。现在有
K
K
K个精灵,每个精灵有两个属性:需要的精灵球和对皮卡丘造成的伤害。
小智的主要目标是收服尽可能多的野生小精灵;如果可以收服的小精灵数量一样,小智希望皮卡丘受到的伤害越小(剩余体力越大),因为他们还要继续冒险。
特别的,皮卡丘体力为0,就不能继续收服精灵了,并且使皮卡丘体力变为0的也不会收服。
求出,最多收服精灵的数量,以及皮卡丘能留下的最大体力
solution:
01背包的性质可以相互转化
例如:
f
[
i
]
f[i]
f[i] 可以表示容量为
i
i
i的最大价值,也可以表示价值为
i
i
i的最小容量
对于此题:
- 状态表示: f [ i ] [ j ] f[i][j] f[i][j]表示 皮卡丘受到 i i i伤害,收了 j j j个精灵的所有方案的最小精灵球数
- 状态计算就是一个二维费用背包的模型了
code:
#include <bits/stdc++.h>
using namespace std;
const int N = 1010, M = 550, S = 105;
int f[M][S];
int n, m, k;
int num, harm;
int main()
{
memset(f, 0x3f, sizeof f);
cin >> n >> m >> k;
f[0][0] = 0;
for(int i = 1; i <= k; ++ i) {
cin >> num >> harm;
for(int j = m; j >= harm; -- j) {
for(int s = k; s >= 1; -- s) {
if(f[j - harm][s - 1] + num <= n) {
f[j][s] = min(f[j][s], f[j - harm][s - 1] + num);
}
}
}
}
// find Answer
for(int s = k; s != -1; --s) {
int p = 0x3f3f3f3f;
for(int j = 0; j < m; ++ j) {// 不能等于m,因为使得皮卡丘体力小于等于0的野生小精灵也不会被小智收服。
if(f[j][s] != 0x3f3f3f3f && j < p) {
p = j;
}
}
if(p != 0x3f3f3f3f) {
cout << s << " " << m - p << "\n";
return 0;
}
}
return 0;
}
4. 数字组合 & 买书
4.1 数字组合:
给定
N
N
N 个正整数
A
1
,
A
2
,
⋯
,
A
N
A_1,A_2,\cdots,A_N
A1,A2,⋯,AN,从中选出若干个数,使它们的和为
M
M
M,求有多少种选择方案。
01背包
- 将总和 M M M看作背包容量, f [ i ] f[i] f[i]表示和为 i i i的方案数;
- 将每个数 A i A_i Ai看作体积 A i A_i Ai的物品
#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
int f[N];
int n, m, x;
int main()
{
cin >> n >> m;
f[0] = 1;
for(int i = 1; i <= n; ++i) {
cin >> x;
for(int j = m; j >= x; -- j) {
f[j] += f[j - x];
}
}
cout << f[m] << "\n";
}
4.2 买书:
小明手里有
n
n
n元钱全部用来买书,书的价格为10元,20元,50元,100元。
问小明有多少种买书方案?(每种书可购买多本)
完全背包:
- f [ i ] f[i] f[i]表示买书恰好 i i i元的方案数
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N];
int a[5] = {0, 10, 20, 50, 100};
int n;
int main()
{
cin >> n;
f[0] = 1;
for(int i = 1; i <= 4; ++ i) {
for(int j = a[i]; j <= n; ++ j)
f[j] += f[j - a[i]];
}
cout << f[n] << "\n";
}
5. 货币系统
题意:
定义一个货币系统
(
n
,
a
)
(n, a)
(n,a):有
n
n
n种货币分别为
a
i
a_i
ai,每种货币能用无限次,进行组合。
两个货币系统是等价的:对于任意一个数
x
x
x,要么都可以被这两个货币系统表示出来,要么都不可以表示出来。
现在给出一个货币系统:
(
n
,
a
)
(n, a)
(n,a),需要找到一个最小的
m
m
m,货币系统
(
m
,
b
)
(m,b)
(m,b)与之等价
solution:
结论:答案中的系统方案一定是由原先的系统方案去掉若干种货币得到
证明:
阿巴阿巴
然后只需找出
(
n
,
a
)
(n,a)
(n,a)系统中哪些是多余的就行。比如说系统中有一个2,那么2的倍数就没有存在的必要了
- 定义: f [ i ] f[i] f[i]表示 i i i有没有必要存在。
- 计算:类似埃筛,把有必要存在的倍数”筛去“
code:
#include <bits/stdc++.h>
using namespace std;
const int N = 110, M = 25010;
int v[N];
bool f[M];
int main()
{
int t;
cin >> t;
while(t -- ) {
int n;
cin >> n;
for(int i = 1; i <= n; ++i) cin >> v[i];
sort(v + 1,v + 1 + n);
int m = v[n], res = 0;
memset(f, false, 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 << "\n";
}
}
6. 机器分配
题意:
有
M
M
M台设备,有
N
N
N个公司。对于第
i
i
i个公司,分配
j
j
j个设备的价值为
w
i
,
j
w_{i,j}
wi,j。问要如何搭配能获得最大价值。
输出最大价值及分配情况
solution:
解法一:背包dp
求最大价值:
每个公司可以看成一个物品组
- f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i个物品组,选了 j j j台设备的所有方案的最大价值
- f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ j − k ] + w i , k ) ( 0 ≤ k ≤ j ) f[i][j] = max(f[i][j], f[i -1][j-k]+w_{i,k})(0 \le k \le j) f[i][j]=max(f[i][j],f[i−1][j−k]+wi,k)(0≤k≤j)
输出路径:
采用了图论的角度:大佬的题解
code
#include <bits/stdc++.h>
using namespace std;
const int N = 20;
int w[N][N], f[N][N];
int n, m;
int path[N], cnt;
void dfs(int i, int j)
{
if(!i) return ;
//寻找当前状态f[i][j]是从上述哪一个f[i-1][k]状态转移过来的
for(int a = 0; a <= j; ++ a) {
if(f[i - 1][j - a] + w[i][a] == f[i][j]) {
path[cnt++] = a;
dfs(i - 1, j - a);
return ;
}
}
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; ++ i) {
for(int j = 1; j <= m; ++ j) {
cin >> w[i][j];
}
}
for(int i = 1; i <= n; ++ i) {
for(int j = 1; j <= m; ++ j) {
for(int k = 0; k <= j; ++ k) {
f[i][j] = max(f[i][j], f[i - 1][j - k] + w[i][k]);
}
}
}
cout << f[n][m] << "\n";
dfs(n, m);
for (int i = cnt - 1, j = 1; i >= 0; -- i, j ++)
cout << j << " " << path[i] << "\n";
}
解法二:dfs
数据范围小,直接爆搜
#include <bits/stdc++.h>
using namespace std;
const int N = 20;
int n, m;
int a[N][N];
int ans_cnt[N], cnt[N], res;
void dfs(int u, int ans, int k) { // 第u个公司,当前价值ans,还有k个设备
if(u == n) {// 到最后一个直接全部给他
ans += a[n][k];
cnt[n] = k;
if(ans > res) { // 更新答案
res = ans;
for(int i = 1; i <= n; ++ i ) ans_cnt[i] = cnt[i];
}
return ;
}
for(int i = 0; i <= k; ++ i) {
cnt[u] = i;
dfs(u + 1, ans + a[u][i], k - i);
}
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; ++ i) {
for(int j = 1; j <= m; ++ j) {
cin >> a[i][j];
}
}
dfs(1, 0, m);
cout << res << "\n";
for(int i = 1; i <= n; ++ i) cout << i << " " << ans_cnt[i] << "\n";
}
7. 金明的预算方案
题意:
有
M
M
M个物品,每个物物品有价格,重要度,主件还是附件三个属性。要买附件就必须先买附件对应的主件。
问:现有
N
N
N元钱,能买到物品的价格乘重要度的最大值。
solution:
解法一:
有依赖的背包 + 分组背包
对于模板,我们是按照子树的体积来划分的。对于此题,显然是不行的(数据范围来到了
3.2
×
1
0
4
3.2×10^4
3.2×104)
但此题的附件最多只有2个,所以我们直接二进制枚举附件,然后就变成了,分组背包,每组为主件+若干个附件,若干个由二进制枚举得到
时间复杂度:
O
(
N
∗
2
2
∗
M
)
O(N*2^2*M)
O(N∗22∗M)
#include <bits/stdc++.h>
using namespace std;
const int N = 32010, M = 66;
int v[M], w[M];
bool good[M];
vector<int> all[M];
int f[N];
int n, m;
int main()
{
cin >> n >> m;
for(int i = 1; i <= m; ++ i) {
int p;
cin >> v[i] >> w[i] >> p;
w[i] *= v[i];
if(p) all[p].push_back(i); // i的主件为p
else good[i] = true;
}
for(int i = 1; i <= m; ++i) { // 找出主件
if(good[i]) {
for(int j = n; j >= 0; -- j) { // 优化成1维逆序dp
int sz = all[i].size(); // 二进制枚举
for(int state = 0; state < 1 << sz; ++ state) {
int v_ = v[i], w_ = w[i];
for(int k = 0; k < sz; ++ k) {
if(state >> k & 1) {
v_ += v[all[i][k]];
w_ += w[all[i][k]];
}
}
if(j - v_ >= 0) f[j] = max(f[j], f[j - v_] + w_);
}
}
}
}
cout << f[n] << "\n";
}
解法二:
因为题目规定了附件的数量至多为2,但是不止2的话。上面的二进制枚举的话可能就会TLE。
但是我们知道分组背包的实质,就是在每组里面进行01背包。
仍然定义:
f
[
i
]
[
j
]
f[i][j]
f[i][j]:考虑前
i
i
i个,物品体积不超过
j
j
j的所有方案中的最大值。
计算:如果选择主件,一定要先对主件这个物品进行一次01背包,然后对于他的所有附件,按照体积划分状态,状态一定>0,做一次类似的01背包
时间复杂度: O ( N ∗ M ∗ M ) O(N*M*M) O(N∗M∗M)
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 32010, M = 65;
int n, m;
PII good[M];
vector<PII> all[M];
int f[M][N];
int main()
{
cin >> n >> m;
for(int i = 1; i <= m; ++ i) {
int v, p, q;
cin >> v >> p >> q;
if(q) all[q].push_back({v, v*p});
else good[i] = {v, v * p};
}
for(int i = 1; i <= m; ++ i) {
if(good[i].first) { // 选择主件,
for(int j = good[i].first; j <= n; ++ j)
f[i][j] = max(f[i][j], f[i - 1][j - good[i].first] + good[i].second);
// 然后在这组里进行01背包,因为以及选了主件,注意状态一定是>0,
for(int k = 0; k < all[i].size(); ++ k) {
for(int j = n; j >= all[i][k].first; -- j)
if(f[i][j - all[i][k].first])
f[i][j] = max(f[i][j], f[i][j - all[i][k].first] + all[i][k].second);
}
}
// 没选主件的时候,记得把状态往下传
for(int j = n; j >= 0; -- j) f[i][j] = max(f[i][j], f[i - 1][j]);
}
cout << f[m][n] << "\n";
}
8. 能量石
题意:
有
N
N
N块能量石,
每个能量石有
E
i
E_i
Ei的能量,吃掉需要
S
i
S_i
Si的时间,每秒损耗
L
i
L_i
Li的能量
如何吃能得到最大的能量
solution:
贪心+01背包
去掉损失的话,是一个01背包模板题
存在损失的话,就需要思考一下吃的顺序,也就是01背包的顺序。
贪心的一般思路:
考虑两个相邻的能量石,
i
j
ij
ij ,易知对于这两个能量石前面以及后面的能量获得与
i
j
ij
ij的顺序是无关的
我们要证明
−
−
−
i
j
−
−
−
--- ij---
−−−ij−−−的顺序比
−
−
−
j
i
−
−
−
---ji---
−−−ji−−−的顺序要更优,也就是:
E
i
+
E
j
−
S
i
∗
L
j
>
E
j
+
E
i
−
S
j
∗
L
i
E_i + E_j - S_i*L_j > E_j + E_i - S_j * L_i
Ei+Ej−Si∗Lj>Ej+Ei−Sj∗Li。
也就是
S
i
∗
L
j
<
S
j
∗
L
i
S_i*L_j < S_j*L_i
Si∗Lj<Sj∗Li。
故按照
S
i
∗
L
j
<
S
j
∗
L
i
S_i*L_j < S_j*L_i
Si∗Lj<Sj∗Li对能量石排序,然后进行01背包
code
#include <bits/stdc++.h>
using namespace std;
const int N = 110, M = 1e4 + 10;
struct Node{
int s, w, l;
bool operator < (const Node &b) const{
return s * b.l < b.s * l;
}
}a[N];
int f[M];
// f[j] 表示 时间为j的所有方案的最大能量
int main()
{
int T; cin >> T;
for(int t = 1; t <= T; ++ t) {
int n, m; cin >> n;
memset(f, -0x3f, sizeof f);
f[0] = m = 0;
for(int i = 1; i <= n; ++ i) cin >> a[i].s >> a[i].w >> a[i].l, m += a[i].s;
sort(a + 1, a + 1 + n);
for(int i = 1; i <= n; ++ i) {
for(int j = m; j >= a[i].s; -- j) {
int pre = j - a[i].s;
f[j] = max(f[j], f[j - a[i].s] + a[i].w - pre * a[i].l);
}
}
cout << "Case #" << t << ": " << *max_element(f, f + 1 + m) << "\n";
}
}