无限循环播放只是周期重复,先用 c mod L 折回单周期,再把压缩串解析成段并用前缀和定位对应音符。
OJ: roj
题目 ID: 20018
难度:普及-
标签:字符串前缀和模拟
日期: 2026-08-28 22:10
形式化题目
给定一个行程长度编码字符串
求
思路
一句话本质:无限循环只是周期重复,任意大的
先看一个直接按题面展开的朴素解,它只适合小数据:
/**
* 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-08-28 21:26
* update_at: 2026-08-28 21:26
*/
#include <bits/stdc++.h>
using namespace std;
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
// 思路:按压缩规则把压缩串直接展开成完整的旋律字符串,
// 再按下标 c % len 直接取第 c 个音符。展开的长度受小数据限制。
string s; // 压缩旋律串
long long c; // 询问的第 c 个音符
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> s >> c;
// 展开:先读一个字母,再把后面连续的数字拼成次数。
string melody;
int n = (int)s.length();
int i = 0;
while (i < n) {
char ch = s[i];
i++;
long long cnt = 0;
while (i < n && s[i] >= '0' && s[i] <= '9') {
cnt = cnt * 10 + (s[i] - '0');
i++;
}
for (long long j = 0; j < cnt; j++) {
melody.push_back(ch);
}
}
// 无限循环 = 周期重复,取余后直接按下标取音符。
long long k = c % (long long)melody.length();
cout << melody[k] << '\n';
return 0;
}问题? 压缩串里的每一段到底代表什么?
每段是"音符 + 连续出现次数":a4b1c2d4 表示 4 个 a、1 个 b、2 个 c、4 个 d。解析时先读一个字母,再把后面连续的数字逐位拼成次数——次数可能占多位(d10 表示 10 个 d,不是 d 和 1 和 0)。这一步只是把输入翻译成
问题? 无限循环播放时,第
循环就是周期重复:旋律每
问题? 知道
给段做前缀和:第 a4 覆盖 b1 覆盖 c2 覆盖 d10 覆盖
问题? 为什么不能真的把旋律展开?
单周期长度最多约
代码
/**
* 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-08-28 21:26
* update_at: 2026-08-28 21:26
*/
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200005;
string s; // 压缩旋律串,只含小写字母和数字
long long c; // 询问的第 c 个音符,从 0 开始编号
int m; // 段的个数
char chs[MAXN]; // chs[i]:第 i 段的音符
long long cnts[MAXN]; // cnts[i]:第 i 段音符连续出现的次数
long long sum; // 单周期总长度 L
// 把压缩串解析成 (音符, 次数) 段,并累加单周期长度。
void parse() {
int n = (int)s.length();
int i = 0;
while (i < n) {
chs[++m] = s[i]; // 当前位置一定是字母(音符)
i++;
long long cnt = 0;
// 次数可能占多位数字,如 d10 表示 10 个 d,逐位拼出来。
while (i < n && s[i] >= '0' && s[i] <= '9') {
cnt = cnt * 10 + (s[i] - '0');
i++;
}
cnts[m] = cnt;
sum += cnt;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> s >> c;
parse();
// 无限循环:第 c 个音符只取决于它在单周期内的位置 k = c mod L。
c %= sum;
// 前缀和定位:第 i 段覆盖区间 [pre, pre + cnts[i]),
// 第一个 pre + cnts[i] > c 的段就是答案段。
long long pre = 0;
for (int i = 1; i <= m; i++) {
if (pre + cnts[i] > c) {
cout << chs[i] << '\n';
return 0;
}
pre += cnts[i];
}
return 0;
}复杂度
- 时间:
,解析一遍压缩串,前缀和定位一遍段数组; - 空间:
,存储段数组 chs[]/cnts[]。
总结
- 循环 = 取模:周期性重复的问题,第一步总是把大下标折回单周期;
- 压缩串解析:行程长度编码的解析要小心多位数字次数,逐位拼数即可;
- 段粒度定位:把"字符下标"查询改成"段区间"查询,一次前缀和扫描完成,不需要真的展开字符串。