按位置、方向和能量直接模拟弹跳,用访问标记统计首次击破的目标。
OJ: usaco
题目 ID: 1372
难度:普及-
标签:模拟状态边界处理usaco
日期: 2026-07-11 16:07
题意
Bessie 在长度为
如果当前能量为
每个位置有两类物体:
- 跳板
:改变方向,并让能量增加 。 - 目标
:如果当前能量至少为 ,这个目标会被击破;同一个目标只计一次。
问 Bessie 无限弹跳直到离开数轴的过程中,最多会击破多少个目标。
思路
先看一个带状态去重的小数据模拟:
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-07-11 16:07
* update_at: 2026-07-11 16:08
*/
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 105;
int n, start_pos;
int q_type[MAXN];
int value_arr[MAXN];
bool broken[MAXN];
set<tuple<long long, int, long long> > seen_state;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> start_pos;
for (int i = 1; i <= n; i++) {
cin >> q_type[i] >> value_arr[i];
}
long long pos = start_pos;
long long power = 1;
int dir = 1;
int ans = 0;
// 小数据暴力:不断模拟,遇到完全相同的状态就说明之后会循环。
while (1 <= pos && pos <= n) {
tuple<long long, int, long long> state = make_tuple(pos, dir, power);
if (seen_state.count(state)) break;
seen_state.insert(state);
int x = (int)pos;
if (q_type[x] == 1) {
if (!broken[x] && power >= value_arr[x]) {
broken[x] = true;
ans++;
}
} else {
dir = -dir;
power += value_arr[x];
}
pos += dir * power;
}
cout << ans << '\n';
return 0;
}暴力记录 (位置, 方向, 能量)。如果同一个状态再次出现,之后的运动轨迹会完全重复,而且目标是否被击破不会影响移动方式,因此可以停止。
满分做法仍然是模拟。每次落到一个位置:
- 如果是目标,且能量足够,并且之前没有击破过,就把答案加一。
- 如果是跳板,就反转方向,并增加能量。
- 按当前方向和能量跳到下一个位置。
问题在于:如果有值为 0 的跳板,Bessie 可能陷入循环,永远不离开数轴。
官方解析的处理方式是:模拟足够多步。原因可以这样理解:
- 如果跳板增加能量为正,能量会变大,跳跃距离增大,不可能在很小的区域里长期拖延。
- 如果陷入由
0跳板造成的循环,运动轨迹会重复,此后不会再击破新的目标。 - 因此跑一个足够大的步数上限后,答案已经稳定。
代码使用官方实现中的 5000000 作为步数上限。对于
代码
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-07-11 16:07
* update_at: 2026-07-11 16:08
*/
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
const int MAX_STEP = 5000000;
int n, start_pos;
int q_type[MAXN]; // 0 表示跳板,1 表示炮击目标
int value_arr[MAXN]; // 跳板增加能量,目标表示击破所需能量
bool broken[MAXN]; // 目标是否已经被击破
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> start_pos;
for (int i = 1; i <= n; i++) {
cin >> q_type[i] >> value_arr[i];
}
long long pos = start_pos;
long long power = 1;
int dir = 1;
int ans = 0;
for (int step = 0; step < MAX_STEP && 1 <= pos && pos <= n; step++) {
int x = (int)pos;
if (q_type[x] == 1) {
if (!broken[x] && power >= value_arr[x]) {
broken[x] = true;
ans++;
}
} else {
dir = -dir;
power += value_arr[x];
}
pos += dir * power;
}
cout << ans << '\n';
return 0;
}复杂度
设模拟步数上限为
时间复杂度为
总结
本题是状态模拟题,关键是把“落地后先处理当前位置,再跳到下一位置”的顺序写对。
目标被击破后仍然可以继续作为普通位置经过,所以需要 broken[] 防止重复计数,但不能改变运动规则。