跳过并跳转到主要内容

回文

对一个包含 $2n$ 个整数(数字 $1 \sim n$ 各出现两次)的序列 $a$ 进行 $2n$ 次弹出操作:每次只能从序列 $a$ 的开头(记为 L)或末尾(记为 R)取出一个数依次加入新序列 $b$,使得最终生成的序列 $b$ 构成回文串。如果存在多种可行的操作方案,需输出字典序最小的操作序列(即包含字符 L 和 R 的字符串);若无法构成回文串则输出 -1。

CSP-S1分钟阅读
半圆形拱顶拜占庭风格马赛克镶嵌画,中心是水盆,两侧对称站立两只孔雀,拱弧上刻有希腊语回文铭文 ΝΙΨΟΝ ΑΝΟΜΗΜΑΤΑ ΜΗ ΜΟΝΑΝ ΟΨΙΝ。
半圆形拱顶拜占庭风格马赛克镶嵌画,中心是水盆,两侧对称站立两只孔雀,拱弧上刻有希腊语回文铭文 ΝΙΨΟΝ ΑΝΟΜΗΜΑΤΑ ΜΗ ΜΟΝΑΝ ΟΨΙΝ。

本题中“字典序最小的操作方案”指的是:由 2n2n 个操作字符('L' 与 'R')拼成的字符串字典序最小。

由于字符的 ASCII 码关系为 'L' << 'R',比较两个方案时,从第 1 个操作字符开始逐位对比:

  • 在第一个不相同的操作位置上,字符为 'L' 的方案字典序更小(优先级更高)。
  • 示例:"LLRLL" 的字典序小于 "LRLLL"(在第 2 位上 'L' << 'R')。

b1b_1 决定了 b2nb_{2n}(二者必须相等)。为了优先保证字典序最小,先尝试第一步选 L(取 a[1]a[1]),找到与 a[1]a[1] 相等的位置 pp;若无解,再尝试第一步选 R(取 a[2n]a[2n])。

假设第一步选了 a[1]a[1],以 a[1]a[1] 的匹配位置 pp 为分界点,将剩下的元素划分为两个独立的双端队列:

  • 左区间:[2,p−1][2, p-1](左端点 L1L_1,右端点 R1R_1)
  • 右区间:[p+1,2n][p+1, 2n](左端点 L2L_2,右端点 R2R_2)

每次从外侧端点(L1L_1 或 R2R_2)挑选一个数,匹配内侧端点(R1R_1 或 L2L_2)的数。按以下优先级依次尝试:

  1. 取 a[L1]a[L_1] 匹配 a[R1]a[R_1](左区间内部匹配) →\rightarrow 前半加 L,后半加 L
  2. 取 a[L1]a[L_1] 匹配 a[L2]a[L_2](左区间左端 匹配 右区间左端) →\rightarrow 前半加 L,后半加 R
  3. 取 a[R2]a[R_2] 匹配 a[R1]a[R_1](右区间右端 匹配 左区间右端) →\rightarrow 前半加 R,后半加 L
  4. 取 a[R2]a[R_2] 匹配 a[L2]a[L_2](右区间内部匹配) →\rightarrow 前半加 R,后半加 R

若这四种匹配均无法满足,说明当前初始选择无解。

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
int n;
vector<int> a;
// 模拟 n-1 步双端队列匹配
bool solve(int l1, int r1, int l2, int r2, string &ans_left, string &ans_right) {
for (int step = 1; step < n; ++step) {
// 1. 优先尝试从左区间的左端 l1 开始匹配 (对应前半段 + 'L')
if (l1 <= r1 && l1 < r1 && a[l1] == a[r1]) { // 匹配左区间右端 r1
l1++;
r1--;
ans_left += 'L';
ans_right += 'L';
} else if (l1 <= r1 && l2 <= r2 && a[l1] == a[l2]) { // 匹配右区间左端 l2
l1++;
l2++;
ans_left += 'L';
ans_right += 'R';
}
// 2. 再尝试从右区间的右端 r2 开始匹配 (对应前半段 + 'R')
else if (l2 <= r2 && l1 <= r1 && a[r2] == a[r1]) { // 匹配左区间右端 r1
r2--;
r1--;
ans_left += 'R';
ans_right += 'L';
} else if (l2 <= r2 && l2 < r2 && a[r2] == a[l2]) { // 匹配右区间左端 l2
r2--;
l2++;
ans_left += 'R';
ans_right += 'R';
} else {
return false;
}
}
return true;
}
void process() {
cin >> n;
a.resize(2 * n + 1);
for (int i = 1; i <= 2 * n; ++i) {
cin >> a[i];
}
// 情况一:第一步选 L(即取出 a[1])
int p1 = -1;
for (int i = 2; i <= 2 * n; ++i) {
if (a[i] == a[1]) {
p1 = i;
break;
}
}
if (p1 != -1) {
// ans_left 记录前半部分(第 1 ~ n 步)的操作,初始选 L (取 a[1])
// ans_right 记录后半部分(第 n+1 ~ 2n 步)的操作
// 这里的 "L" 代表最后一步(第 2n 步)取出 a[p1] 的操作:
// 此时序列只剩唯一元素 a[p1],左右端点等价,为满足字典序最小优先记为 'L'
// (注:ans_right 在追加完后续操作后会整体 reverse,初始的 "L" 会翻转到最终字符串的末尾)
string ans_left = "L", ans_right = "L";
// 左区间 [2, p1-1],右区间 [p1+1, 2*n]
if (solve(2, p1 - 1, p1 + 1, 2 * n, ans_left, ans_right)) {
// 将倒序收集的后半段(第 2n ~ n+1 步)翻转为正确的正序(第 n+1 ~ 2n 步)
reverse(ans_right.begin(), ans_right.end());
cout << ans_left << ans_right << "\n";
return;
}
}
// 情况二:第一步选 R(即取出 a[2*n])
int p2 = -1;
for (int i = 1; i < 2 * n; ++i) {
if (a[i] == a[2 * n]) {
p2 = i;
break;
}
}
if (p2 != -1) {
string ans_left = "R", ans_right = "L";
// 左区间 [1, p2-1],右区间 [p2+1, 2*n-1]
if (solve(1, p2 - 1, p2 + 1, 2 * n - 1, ans_left, ans_right)) {
reverse(ans_right.begin(), ans_right.end());
cout << ans_left << ans_right << "\n";
return;
}
}
cout << -1 << "\n";
}
int main() {
// 快速 IO
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
process();
}
return 0;
}

  • 时间复杂度:每一组数据只需移动双指针匹配 n 次,故单组数据复杂度为 O(n)。处理全部测试点耗时大约在几十毫秒以内。
  • 空间复杂度:存储数组 a 和答案串,空间复杂度为 O(n)。

© 2026 三七开 · 一方通行,只管向前。

旅途由 Astro 驱动 · 主题 Chirping Astro