按操作顺序直接模拟移动、过路结算、建造升级和轮末收益,维护建筑拥有者、等级与价值即可。
OJ: luogu
题目 ID: P8874
难度:普及/提高-
标签:模拟思维
日期: 2026-06-19 01:38
题意
有两个玩家在一个环形地图上移动。
每次行动会:
- 按骰子点数走若干步;
- 路过自己的建筑就收钱,路过对方的建筑就付钱;
- 停下后,可能在当前位置建造或升级自己的建筑;
- 每轮两个人都行动完后,所有建筑还会再给拥有者一笔固定收益。
如果某个人在自己行动过程中资金变成负数,这个人立刻输掉游戏。
给出整个操作序列,问谁会输;如果一直没人输,就输出最后两人的资金。
思路
先看一个可以直接验证想法的朴素解:
严格按题意把整场游戏一步一步模拟出来,维护:
- 两人的当前位置
- 两人的资金
- 每个格子是否有建筑
- 建筑属于谁、等级是多少、当前价值
a_i是多少
cpp
// brute.cpp:按题目规则直接模拟整场游戏。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 105;
const int MAXL = 105;
const int RENKO = 0;
const int MERRY = 1;
struct Op {
int type;
int val;
};
int n, q, L;
long long m;
int c[MAXN][MAXL];
int d[MAXN];
vector<Op> ops;
int pos_[2];
long long money[2];
int owner[MAXN];
int level_[MAXN];
long long value_[MAXN];
void give_round_income() {
for (int i = 1; i <= n; i++) {
if (owner[i] != -1) {
money[owner[i]] += d[i];
}
}
}
bool move_player(int who, int step) {
int other = who ^ 1;
for (int i = 1; i <= step; i++) {
pos_[who]++;
if (pos_[who] > n) {
pos_[who] = 1;
}
int p = pos_[who];
if (owner[p] == who) {
money[who] += value_[p];
}
else if (owner[p] == other) {
money[who] -= value_[p];
money[other] += value_[p];
if (money[who] < 0) {
return false;
}
}
}
return true;
}
void build_or_upgrade(int who, int times) {
int p = pos_[who];
if (owner[p] != -1 && owner[p] != who) {
return;
}
if (owner[p] == -1) {
if (times >= 1 && money[who] >= c[p][0]) {
money[who] -= c[p][0];
owner[p] = who;
level_[p] = 1;
value_[p] = c[p][0];
times--;
}
else {
return;
}
}
for (int t = 1; t <= times; t++) {
if (level_[p] >= L) {
break;
}
if (money[who] < c[p][level_[p]]) {
break;
}
money[who] -= c[p][level_[p]];
value_[p] += c[p][level_[p]];
level_[p]++;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m >> q >> L;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= L - 1; j++) {
cin >> c[i][j];
}
}
for (int i = 1; i <= n; i++) {
cin >> d[i];
}
while (true) {
Op op;
if (!(cin >> op.type >> op.val)) {
break;
}
ops.push_back(op);
}
for (int i = 1; i <= n; i++) {
owner[i] = -1;
}
pos_[RENKO] = 1;
pos_[MERRY] = 1;
money[RENKO] = m;
money[MERRY] = m;
int move_cnt = 0;
for (int i = 0; i < (int)ops.size(); i++) {
if (ops[i].type != 1) {
continue;
}
int who = move_cnt % 2;
if (!move_player(who, ops[i].val)) {
if (who == RENKO) {
cout << "Renko\n";
}
else {
cout << "Merry\n";
}
return 0;
}
if (i + 1 < (int)ops.size() && ops[i + 1].type == 2) {
build_or_upgrade(who, ops[i + 1].val);
i++;
}
move_cnt++;
if (move_cnt % 2 == 0) {
give_round_income();
}
}
cout << money[RENKO] << ' ' << money[MERRY] << '\n';
return 0;
}这题本质上没有隐藏算法,重点是把规则实现完整、顺序实现正确。
真正容易出错的地方主要有四个:
1 k操作才会切换行动者,2 k不会切换;- 每个
1 k后面至多跟一个2 k,所以要按顺序读操作; - 路过格子时,终点格子也算“经过”;
- 每两次
1 k操作结束后,才统一结算一轮建筑收益。
因此正式做法依然就是模拟,只是把几个动作拆清楚:
- 处理
1 k:逐步移动,并在经过每个格子时结算建筑效果; - 如果后面紧跟
2 k:尝试建造/升级; - 每完成两次
1 k:遍历所有建筑,给拥有者发一轮固定收益; - 过程中一旦当前行动者资金变成负数,就立即输出输家。
因为题目规模只有 n,L <= 100,q <= 10^4,直接模拟完全足够。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 105;
const int MAXL = 105;
const int RENKO = 0;
const int MERRY = 1;
struct Op {
int type;
int val;
};
int n, q, L;
long long m;
int c[MAXN][MAXL];
int d[MAXN];
vector<Op> ops;
int pos_[2];
long long money[2];
int owner[MAXN]; // -1 表示无建筑,0 表示莲子,1 表示梅莉
int level_[MAXN]; // 建筑当前等级
long long value_[MAXN]; // 当前建筑的 a_i
void give_round_income() {
for (int i = 1; i <= n; i++) {
if (owner[i] != -1) {
money[owner[i]] += d[i];
}
}
}
bool move_player(int who, int step) {
int other = who ^ 1;
for (int i = 1; i <= step; i++) {
pos_[who]++;
if (pos_[who] > n) {
pos_[who] = 1;
}
int p = pos_[who];
if (owner[p] == who) {
money[who] += value_[p];
}
else if (owner[p] == other) {
money[who] -= value_[p];
money[other] += value_[p];
if (money[who] < 0) {
return false;
}
}
}
return true;
}
void build_or_upgrade(int who, int times) {
int p = pos_[who];
if (owner[p] != -1 && owner[p] != who) {
return;
}
if (owner[p] == -1) {
if (times >= 1 && money[who] >= c[p][0]) {
money[who] -= c[p][0];
owner[p] = who;
level_[p] = 1;
value_[p] = c[p][0];
times--;
}
else {
return;
}
}
while (times > 0) {
if (level_[p] >= L) {
break;
}
if (money[who] < c[p][level_[p]]) {
break;
}
money[who] -= c[p][level_[p]];
value_[p] += c[p][level_[p]];
level_[p]++;
times--;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m >> q >> L;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= L - 1; j++) {
cin >> c[i][j];
}
}
for (int i = 1; i <= n; i++) {
cin >> d[i];
}
while (true) {
Op op;
if (!(cin >> op.type >> op.val)) {
break;
}
ops.push_back(op);
}
for (int i = 1; i <= n; i++) {
owner[i] = -1;
}
pos_[RENKO] = 1;
pos_[MERRY] = 1;
money[RENKO] = m;
money[MERRY] = m;
int move_cnt = 0;
for (int i = 0; i < (int)ops.size(); i++) {
if (ops[i].type != 1) {
continue;
}
int who = move_cnt % 2;
if (!move_player(who, ops[i].val)) {
if (who == RENKO) {
cout << "Renko\n";
}
else {
cout << "Merry\n";
}
return 0;
}
if (i + 1 < (int)ops.size() && ops[i + 1].type == 2) {
build_or_upgrade(who, ops[i + 1].val);
i++;
}
move_cnt++;
if (move_cnt % 2 == 0) {
give_round_income();
}
}
cout << money[RENKO] << ' ' << money[MERRY] << '\n';
return 0;
}复杂度
设一共读到 2q 次移动操作,总移动步数之和为 S。
时间复杂度是
总结
这题的关键不是优化,而是规则实现的顺序必须绝对准确。
只要把“移动结算、建造升级、轮末收益、输家判定”这几个时机分开写清楚,代码就会很稳。
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。

