把字符替换函数视为字符集合上的置换,用二进制倍增预处理字符经过 $2^j$ 次替换后的结果。
OJ: shumeng
题目 ID: CSP202409B
难度:普及-
标签:字符串置换倍增
日期: 2026-07-31 16:21
形式化题目
给定一个字符替换函数:每个字符替换为另一个字符(未定义的字符保持不变),以及一个初始字符串
注意字符串与字符对都用 # 包裹,字符串内部可能含空格,需要按行读取。
思路
朴素做法:直接重复替换
先看直接模拟:每轮把整串字符同时替换一次,重复
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-31 16:21
* update_at: 2026-08-17 22:39
*/
// brute.cpp:小数据暴力解,直接重复执行 k 次字符替换,只适合 k 很小的数据。
#include <bits/stdc++.h>
using namespace std;
const int MAXC = 128;
int next_char[MAXC]; // 每个字符经过一次替换后的字符
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
// 输入行形如 #Hello World#,需要整行读取
string line;
getline(cin, line);
string initial = line.substr(1, line.size() - 2); // 去掉两侧的 # 得到初始字符串
int n;
cin >> n;
getline(cin, line);
// 未定义的字符替换为自身
for (int i = 0; i < MAXC; i++) next_char[i] = i;
for (int i = 0; i < n; i++) {
getline(cin, line); // 形如 #a b# 或 #x y#
next_char[(unsigned char)line[1]] = (unsigned char)line[2];
}
int m;
cin >> m;
for (int query = 0; query < m; query++) {
int k;
cin >> k;
// 每轮把整串字符同时替换一次
string result = initial;
for (int step = 0; step < k; step++) {
for (int i = 0; i < (int)result.size(); i++) {
result[i] = (char)next_char[(unsigned char)result[i]];
}
}
cout << '#' << result << "#\n";
}
return 0;
}复杂度与
把替换看成字符函数
建立 next_char[ch]:未定义的字符指向自身,输入的字符对覆盖对应映射。函数作用在有限的字符集合上,是一个置换,字符永远只会在这有限集合中移动。
二进制倍增
定义 jump[j][ch] 表示字符 ch 经过
预处理 1 就把当前字符替换为 jump[j][current]。初始字符串的每个字符独立完成这个过程,最后补回两侧的 # 输出。
代码
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-31 16:21
* update_at: 2026-08-17 22:39
*/
#include <bits/stdc++.h>
using namespace std;
const int MAXC = 128; // ASCII 字符范围
const int LOG = 31; // 2^30 > 1e9,31 层足够覆盖最大变换次数
int next_char[MAXC]; // next_char[c] 表示字符 c 经过一次替换后的字符
int jump[LOG][MAXC]; // jump[j][c] 表示字符 c 经过 2^j 次替换后的字符
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
// 输入行形如 #Hello World#,需要整行读取
string line;
getline(cin, line);
string initial = line.substr(1, line.size() - 2); // 去掉两侧的 # 得到初始字符串
int n;
cin >> n;
getline(cin, line);
// 未定义的字符替换为自身
for (int i = 0; i < MAXC; i++) next_char[i] = i;
for (int i = 0; i < n; i++) {
getline(cin, line); // 形如 #a b# 或 #x y#
next_char[(unsigned char)line[1]] = (unsigned char)line[2];
}
// 预处理跳转表:jump[j][c] = jump[j-1][ jump[j-1][c] ]
for (int i = 0; i < MAXC; i++) jump[0][i] = next_char[i];
for (int bit = 1; bit < LOG; bit++) {
for (int i = 0; i < MAXC; i++) {
jump[bit][i] = jump[bit - 1][jump[bit - 1][i]];
}
}
int m;
cin >> m;
for (int query = 0; query < m; query++) {
long long k;
cin >> k;
// 每个字符独立地完成 k 次替换:把 k 按二进制拆成若干 2^j 段
string result;
for (int i = 0; i < (int)initial.size(); i++) {
int current = (unsigned char)initial[i];
long long steps = k;
int bit = 0;
while (steps > 0) {
if (steps & 1) current = jump[bit][current];
steps >>= 1;
bit++;
}
result.push_back((char)current);
}
cout << '#' << result << "#\n";
}
return 0;
}复杂度
设初始字符串长度为
- 时间:跳转表预处理
;每次查询每个字符做 次跳转,总时间复杂度 。 - 空间:跳转表
,字符串 。
总结
重复应用同一个字符函数会形成周期,但不需要显式求周期。二进制倍增把