T4. 激情俯冲
你喜欢坐过山车吗?当过山车慢慢爬高,然后从最高点快速冲下来,这就是一次“激情俯冲”。
过山车轨道一共分成了 \(n\) 段(\(n \geqslant 3\)),每一段都有一个高度,分别是 \(h_1, h_2, \cdots, h_n\)。如果其中连续的若干段轨道高度先上升再下降,那么这几段就形成了一次“激情俯冲”。
具体来说,如果有两个位置 \(a\) 和 \(b\),并且存在一个位置 \(s\)(满足
\(1 \leqslant a < s < b \leqslant n\)),使得:从第 \(a\) 段到第 \(s\) 段,高度一直在增加:\(h_a < h_{a+1} < \cdots < h_s\);然后从第 \(s\) 段到第 \(b\) 段,高度一直在减少:\(h_s > h_{s+1} > \cdots > h_b\)。那么从第 \(a\) 段到第 \(b\) 段,就形成了一次长度为 \(b−a+1\) 的“激情俯冲”路段。
现在,请你找出整条过山车轨道上最长的“激情俯冲”路段有多长?
限制:
- \(1 \leqslant n \leqslant 5 \times 10^5\)
- \(1 \leqslant h_i \leqslant 10^9\)
算法分析
预处理出以 \(i\) 结尾的最长升高段以及以 \(i\) 开头的最长下降段
然后把左右拼在一起即可
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
int main() {
int n;
cin >> n;
vector<int> h(n+2);
rep(i, n) cin >> h[i];
vector<int> l(n+1), r(n+2);
l[0] = r[n+1] = -1;
rep(i, n) if (h[i] > h[i-1]) l[i] = l[i-1]+1;
for (int i = n; i >= 1; --i) {
if (h[i] > h[i+1]) r[i] = r[i+1]+1;
}
int ans = 0;
rep(i, n) {
if (l[i] and r[i]) {
ans = max(ans, l[i]+r[i]+1);
}
}
cout << ans << '\n';
return 0;
}
T5. 怪物猎人
皮皮是一名忠实的游戏爱好者,最近他迷上了一款叫作《怪物猎人》的动作角色扮演游戏。游戏的内容很简单,只要不停地制造武器打怪就好了。
这个游戏一共有 \(N\) 个怪物,分别有着不同的等级。皮皮需要在 \(K\) 天内按顺序把它们都打败。每一天,皮皮要做的第一件事情就是打造一把武器,而武器也有对应的等级,如果武器的等级低于怪物,那么皮皮就打不过那个怪物,否则皮皮就能战胜它。
已知皮皮每天只会去一次武器铺,购买任意等级的武器,然后去打一整天的怪物。但是每用一个武器打败一个怪物后,就需要支付与武器等级同样的金币来修理武器,注意:即使是击杀最后一个怪物也需要修理武器。
现在皮皮已经知道了 \(N\) 个怪物的等级,他想知道自己最少需要花费多少枚金币,能在 \(K\) 天内击杀所有的怪物。
限制:
- \(1 \leqslant K \leqslant N \leqslant 500\)
- \(1 \leqslant T_i \leqslant 10^5\)
算法分析
每天花费的金币就是就是当天使用的武器的最大等级 \(\times\) 打的怪的数量
\(50\) 分做法:爆搜
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
int main() {
int n, k;
cin >> n >> k;
vector<int> t(n);
rep(i, n) cin >> t[i];
int ans = 1e9;
// cnt: 记录已经打了几天
// sum: 记录当前已经花费了多少金币
auto dfs = [&](auto& f, int i, int cnt=0, int sum=0) -> void {
if (cnt > k) return;
if (i == n) {
ans = min(ans, sum);
return;
}
int mx = 0;
for (int j = i; j < n; ++j) {
mx = max(mx, t[j]);
f(f, j+1, cnt+1, sum+mx*(j-i+1));
}
};
dfs(dfs, 0);
cout << ans << '\n';
return 0;
}
\(100\) 分做法:
上面的爆搜做法就是把复杂问题转化成很多个简单的子问题来解决
而dp也是同样的思想
记 dp[r][i] 表示击败前 \(r\) 只怪物花了 \(i\) 天所需的最少金币数
状态转移:
\( dp[r][i] = \min\{dp[l-1][i-1] + mx \times (r-l+1)\} \)
时间复杂度为 \(O(N^3)\)
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
inline void chmin(int& x, int y) { if (x > y) x = y; }
int main() {
int n, k;
cin >> n >> k;
vector<int> t(n+1);
rep(i, n) cin >> t[i];
const int INF = 1001001001;
vector<vector<int>> dp(n+1, vector<int>(k+1, INF));
dp[0][0] = 0;
rep(r, n) {
int mx = 0;
for (int l = r; l >= 1; --l) {
mx = max(mx, t[l]);
for (int i = 1; i <= min(r, k); ++i) {
chmin(dp[r][i], dp[l-1][i-1] + mx*(r-l+1));
}
}
}
int ans = INF;
rep(i, k) chmin(ans, dp[n][i]);
cout << ans << '\n';
return 0;
}
T6. 白金矿工
小猴是一名忠实的游戏爱好者,最近他迷上了一款叫作《白金矿工》的休闲类游戏。游戏规则非常简单,小猴在游戏开始的时候拥有一个矿镐,地图上有 \(N\) 个矿石可以供小猴选择抓取,每次抓取需要消耗一个矿镐,每个矿石最多被抓取一次,并且会带来一些金币奖励(或惩罚),另外有可能会奖励额外的矿镐。
已知 \(i\) 号矿石在被抓取了以后分别能为小猴提供 \(A_i\) 的金币和 \(B_i\) 个矿镐。如果 \(A_i\) 小于0,代表这次抓取会扣除金币,不需要担心金币总数小于 \(0\),游戏允许暂时欠一些金币。小猴想知道这一回合自己最多能够获得多少金币?(不一定要用完所有矿镐)
限制:
- \(1 < N \leqslant 1000\)
- \(-10^9 \leqslant A_i \leqslant 10^9\)
- \(0 \leqslant B_i \leqslant N\)
注意,过程中镐子数量不超过 \(N\) 个。
算法分析
先将所有矿石按 \(B_i\) 从多到少进行排序
排序后,对每个矿石决定选或不选,dfs 深搜模型
时间复杂度为 \(O(2^N)\),可以得到 \(30\) 分
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
struct Stone {
int a, b;
Stone(int a=0, int b=0): a(a), b(b) {}
bool operator<(const Stone& o) const {
return b > o.b;
}
};
int main() {
cin.tie(nullptr) -> sync_with_stdio(false);
int n;
cin >> n;
vector<Stone> s(n);
rep(i, n) cin >> s[i].a >> s[i].b;
sort(s.begin(), s.end());
ll ans = 0;
// cnt: 当前剩余的矿镐数量
// sum: 当前获得的金币数
auto dfs = [&](auto& f, int i, int cnt=1, ll sum=0) -> void {
if (i == n) {
ans = max(ans, sum);
return;
}
// 不选
f(f, i+1, cnt, sum);
// 选
if (cnt) {
f(f, i+1, cnt-1+s[i].b, sum+s[i].a);
}
};
dfs(dfs, 0);
cout << ans << '\n';
return 0;
}
\(100\) 分做法
注意到当 dfs 中的前 \(2\) 项相同时,显然只需留下 \(sum\) 最大的那个状态
考虑dp
定义 dp[i][j] 表示从前 \(i\) 个矿石中挑选若干个满足当前矿镐数量为 \(j\) 时的最大的 \(sum\)
转移方程:
\( dp[i][j] = \max(dp[i-1][j], dp[i-1][j+1-b_i] + a_i) \)
其实就是01背包
时间复杂度为 \(O(N^2)\)
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
struct Stone {
int a, b;
Stone(int a=0, int b=0): a(a), b(b) {}
bool operator<(const Stone& o) const {
return b > o.b;
}
};
inline void chmax(ll& x, ll y) { if (x < y) x = y; }
int main() {
cin.tie(nullptr) -> sync_with_stdio(false);
int n;
cin >> n;
vector<Stone> s(n);
rep(i, n) cin >> s[i].a >> s[i].b;
sort(s.begin(), s.end());
ll ans = 0;
vector<ll> dp(n+1, -1e18);
dp[1] = 0;
int sum = 0;
rep(i, n) {
vector<ll> old(n+1, -1e18);
swap(dp, old);
sum += s[i].b;
rep(j, sum+1) {
dp[j] = old[j];
if (j-s[i].b+1 >= 0) {
chmax(dp[j], old[j-s[i].b+1]+s[i].a);
}
chmax(ans, dp[j]);
}
}
cout << ans << '\n';
return 0;
}
T7. 矿洞寻宝
小猴是一位优秀的探险家,某天他根据地图来到了一个埋藏着宝藏的区域。这片区域是一个有 \(n\) 行 \(m\) 列的网格区域(行列均从 \(1\) 开始编号),每个格子的位置都有一个洞穴,用 \((i,j)\) 表示第 \(i\) 行第 \(j\) 列的洞穴;
对于每个位置洞穴,都存在长度为 \(1\) 的轨道与它上下左右相邻的洞穴相连接;也就是说一个位置 \((i,j)\) 洞穴可以通过轨道移动到以下 \(4\) 个位置:
- 向上移动 \(1\) 格到达 \((i−1,j)\);
- 向下移动 \(1\) 格到达 \((i+1,j)\);
- 向左移动 \(1\) 格到达 \((i,j−1)\);
- 向右移动 \(1\) 格到达 \((i,j+1)\);
当然,在任何时刻,小猴不能够移动到网格区域之外(即超过行列编号范围);而某些洞穴由于历史过于悠久,可能已经被石头堵住了(称作封闭洞穴),小猴也没办法去到被封闭的洞穴中。
在所有未封闭的洞穴中,其中有 \(k\) 个不同的洞穴各藏有一个宝藏,每个宝藏都有一个重量 \(w\);同时,小猴有一个矿车,可以通过轨道在洞穴之间任意移动去获取宝藏;
每当小猴到达一个藏有宝藏的洞穴后,他可以选择拿起这个宝藏,也可以选择不拿;如果选择拿起宝藏(假设重量为 \(w\)),那么矿车的重量 \(T\) 会增加 \(w\);矿车每移动 \(1\) 的距离就会消耗 \(T\) 单位的燃油(\(T\) 是矿车当前的重量),初始时,重量 \(T\) 为 \(0\)。
小猴可以任意选择一个洞穴作为起点去寻宝,他的目标是获得所有的 \(k\) 个宝藏;当他达到目标时,他可以马上结束寻宝。
但是小猴很吝啬,他想要知道在满足“获得所有 \(k\) 个宝藏”的前提下,最少需要准备多少单位的燃油,你能够帮帮他吗?
限制:
- \(1 \leqslant n, m \leqslant 700\)
- \(1 \leqslant k \leqslant \min(n \times m, 10)\)
- \(1 \leqslant x \leqslant n\)
- \(1 \leqslant y \leqslant m\)
- \(1 \leqslant w \leqslant 50\)
算法分析
显然一定选有宝藏的格子作为起点和终点
当 \(k = 2\) 时,一定是从重量小的宝藏走到重量大的宝藏,走一遍最短路
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
struct P {
int x, y;
P(int x=0, int y=0): x(x), y(y) {}
};
const int dx[] = {0, 1, 0, -1};
const int dy[] = {1, 0, -1, 0};
int main() {
int n, m, k;
cin >> n >> m >> k;
vector<vector<int>> a(n, vector<int>(m));
rep(i, n)rep(j, m) cin >> a[i][j];
vector<P> b(n);
vector<int> w(k);
rep(i, k) cin >> b[i].x >> b[i].y >> w[i];
rep(i, k) b[i].x--, b[i].y--;
if (k == 1) {
puts("0");
return 0;
}
const int INF = 1001001001;
vector<vector<int>> dist(n, vector<int>(m, INF));
queue<P> q;
dist[b[0].x][b[0].y] = 0;
q.push(b[0]);
while (q.size()) {
auto [x, y] = q.front(); q.pop();
rep(v, 4) {
int nx = x+dx[v], ny = y+dy[v];
if (nx < 0 or ny < 0 or nx >= n or ny >= m or a[nx][ny]) continue;
if (dist[nx][ny] != INF) continue;
dist[nx][ny] = dist[x][y]+1;
q.emplace(nx, ny);
}
}
int ans = dist[b[1].x][b[1].y]*min(w[0], w[1]);
cout << ans << '\n';
return 0;
}
测试点 \(3 \sim 4\):注意到所有洞穴都是未封闭的,那么就不需要跑bfs求最短路,两个宝藏的最短路就是这两个点的曼哈顿距离
\(k\) 最大只有 \(5\),可以对这 \(5\) 个宝藏做一遍全排列
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
int main() {
int n, m, k;
cin >> n >> m >> k;
vector<vector<int>> a(n, vector<int>(m));
rep(i, n)rep(j, m) cin >> a[i][j];
vector<int> x(k), y(k), w(k);
rep(i, k) cin >> x[i] >> y[i] >> w[i];
if (k == 1) {
puts("0");
return 0;
}
vector<int> p(k);
rep(i, k) p[i] = i;
int ans = 1e9;
do {
int now = 0, t = 0;
rep(i, k-1) {
int d = abs(x[p[i+1]]-x[p[i]]) + abs(y[p[i+1]]-y[p[i]]);
t += w[p[i]];
now += d*t;
}
ans = min(ans, now);
} while (next_permutation(p.begin(), p.end()));
cout << ans << '\n';
return 0;
}
测试点 \(5 \sim 7\):最短路再换成用bfs去求
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
struct P {
int x, y;
P(int x=0, int y=0): x(x), y(y) {}
};
const int dx[] = {0, 1, 0, -1};
const int dy[] = {1, 0, -1, 0};
int main() {
cin.tie(nullptr) -> sync_with_stdio(false);
int n, m, k;
cin >> n >> m >> k;
vector<vector<int>> a(n, vector<int>(m));
rep(i, n)rep(j, m) cin >> a[i][j];
vector<P> b(k);
vector<int> w(k);
rep(i, k) cin >> b[i].x >> b[i].y >> w[i];
rep(i, k) b[i].x--, b[i].y--;
if (k == 1) {
puts("0");
return 0;
}
vector<int> p(k);
rep(i, k) p[i] = i;
const int INF = 1001001001;
auto bfs = [&](P s, P t) {
vector<vector<int>> dist(n, vector<int>(m, INF));
queue<P> q;
dist[s.x][s.y] = 0;
q.push(s);
while (q.size()) {
auto [x, y] = q.front(); q.pop();
rep(v, 4) {
int nx = x+dx[v], ny = y+dy[v];
if (nx < 0 or ny < 0 or nx >= n or ny >= m or a[nx][ny]) continue;
if (dist[nx][ny] != INF) continue;
dist[nx][ny] = dist[x][y]+1;
q.emplace(nx, ny);
}
}
return dist[t.x][t.y];
};
int ans = INF;
do {
int now = 0, t = 0;
rep(i, k-1) {
int d = bfs(b[p[i+1]], b[p[i]]);
t += w[p[i]];
now += d*t;
}
ans = min(ans, now);
} while (next_permutation(p.begin(), p.end()));
cout << ans << '\n';
return 0;
}
\(100\) 分做法
可以提前预处理出任意两个宝藏间的最短路
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
struct P {
int x, y;
P(int x=0, int y=0): x(x), y(y) {}
};
const int dx[] = {0, 1, 0, -1};
const int dy[] = {1, 0, -1, 0};
int main() {
cin.tie(nullptr) -> sync_with_stdio(false);
int n, m, k;
cin >> n >> m >> k;
vector<vector<int>> a(n, vector<int>(m));
rep(i, n)rep(j, m) cin >> a[i][j];
vector<P> b(k);
vector<int> w(k);
rep(i, k) cin >> b[i].x >> b[i].y >> w[i];
rep(i, k) b[i].x--, b[i].y--;
vector<int> p(k);
rep(i, k) p[i] = i;
const int INF = 1001001001;
auto bfs = [&](P s) {
vector<vector<int>> dist(n, vector<int>(m, INF));
queue<P> q;
dist[s.x][s.y] = 0;
q.push(s);
while (q.size()) {
auto [x, y] = q.front(); q.pop();
rep(v, 4) {
int nx = x+dx[v], ny = y+dy[v];
if (nx < 0 or ny < 0 or nx >= n or ny >= m or a[nx][ny]) continue;
if (dist[nx][ny] != INF) continue;
dist[nx][ny] = dist[x][y]+1;
q.emplace(nx, ny);
}
}
return dist;
};
vector<vector<vector<int>>> dists(k);
rep(i, k) dists[i] = bfs(b[i]);
int ans = INF;
do {
int now = 0, t = 0;
rep(i, k-1) {
int d = dists[p[i]][b[p[i+1]].x][b[p[i+1]].y];
t += w[p[i]];
now += d*t;
}
ans = min(ans, now);
} while (next_permutation(p.begin(), p.end()));
cout << ans << '\n';
return 0;
}
浙公网安备 33010602011771号