2021信奥赛C++提高组csp-s复赛真题及题解:回文

2021信奥赛C++提高组csp-s复赛真题及题解:回文

题目描述

给定正整数 n n n 和整数序列 a 1 , a 2 , ... , a 2 n a_1, a_2, \ldots, a_{2 n} a1,a2,...,a2n,在这 2 n 2 n 2n 个数中, 1 , 2 , ... , n 1, 2, \ldots, n 1,2,...,n 分别各出现恰好 2 2 2 次。现在进行 2 n 2 n 2n 次操作,目标是创建一个长度同样为 2 n 2 n 2n 的序列 b 1 , b 2 , ... , b 2 n b_1, b_2, \ldots, b_{2 n} b1,b2,...,b2n,初始时 b b b 为空序列,每次可以进行以下两种操作之一:

  1. 将序列 a a a 的开头元素加到 b b b 的末尾,并从 a a a 中移除。
  2. 将序列 a a a 的末尾元素加到 b b b 的末尾,并从 a a a 中移除。

我们的目的是让 b b b 成为一个回文数列 ,即令其满足对所有 1 ≤ i ≤ n 1 \le i \le n 1≤i≤n,有 b i = b 2 n + 1 − i b_i = b_{2 n + 1 - i} bi=b2n+1−i。请你判断该目的是否能达成,如果可以,请输出字典序最小的操作方案,具体在【输出格式】中说明。

输入格式

每个测试点包含多组测试数据。

输入的第一行,包含一个整数 T T T,表示测试数据的组数。对于每组测试数据:

第一行,包含一个正整数 n n n。

第二行,包含 2 n 2 n 2n 个用空格隔开的整数 a 1 , a 2 , ... , a 2 n a_1, a_2, \ldots, a_{2 n} a1,a2,...,a2n。

输出格式

对每组测试数据输出一行答案。

如果无法生成出回文数列,输出一行 -1,否则输出一行一个长度为 2 n 2 n 2n 的、由字符 LR 构成的字符串(不含空格),其中 L 表示移除开头元素的操作 1,R 表示操作 2。

你需要输出所有方案对应的字符串中字典序最小的一个。

字典序的比较规则如下:长度均为 2 n 2 n 2n 的字符串 s 1 ∼ 2 n s_{1 \sim 2 n} s1∼2n 比 t 1 ∼ 2 n t_{1 \sim 2 n} t1∼2n 字典序小,当且仅当存在下标 1 ≤ k ≤ 2 n 1 \le k \le 2 n 1≤k≤2n 使得对于每个 1 ≤ i < k 1 \le i < k 1≤i<k 有 s i = t i s_i = t_i si=ti 且 s k < t k s_k < t_k sk<tk。

输入输出样例 1
输入 1
复制代码
2
5
4 1 2 4 5 3 1 2 3 5
3
3 2 1 2 1 3
输出 1
复制代码
LRRLLRRRRL
-1
说明/提示

【样例解释 1】

在第一组数据中,生成的的 b b b 数列是 [ 4 , 5 , 3 , 1 , 2 , 2 , 1 , 3 , 5 , 4 ] [4, 5, 3, 1, 2, 2, 1, 3, 5, 4] [4,5,3,1,2,2,1,3,5,4],可以看出这是一个回文数列。

另一种可能的操作方案是 LRRLLRRRRR,但比答案方案的字典序要大。

【数据范围】

令 ∑ n \sum n ∑n 表示所有 T T T 组测试数据中 n n n 的和。

对所有测试点保证 1 ≤ T ≤ 100 1 \le T \le 100 1≤T≤100, 1 ≤ n , ∑ n ≤ 5 × 10 5 1 \le n, \sum n \le 5 \times {10}^5 1≤n,∑n≤5×105。

测试点编号 T ≤ T \le T≤ n ≤ n \le n≤ ∑ n ≤ \sum n \le ∑n≤ 特殊性质
1 ∼ 7 1 \sim 7 1∼7 10 10 10 10 10 10 50 50 50
8 ∼ 10 8 \sim 10 8∼10 100 100 100 20 20 20 1000 1000 1000
11 ∼ 12 11 \sim 12 11∼12 100 100 100 100 100 100 1000 1000 1000
13 ∼ 15 13 \sim 15 13∼15 100 100 100 1000 1000 1000 25000 25000 25000
16 ∼ 17 16 \sim 17 16∼17 1 1 1 5 × 10 5 5 \times {10}^5 5×105 5 × 10 5 5 \times {10}^5 5×105
18 ∼ 20 18 \sim 20 18∼20 100 100 100 5 × 10 5 5 \times {10}^5 5×105 5 × 10 5 5 \times {10}^5 5×105
21 ∼ 25 21 \sim 25 21∼25 100 100 100 5 × 10 5 5 \times {10}^5 5×105 5 × 10 5 5 \times {10}^5 5×105

特殊性质:如果我们每次删除 a a a 中两个相邻且相等的数,存在一种方式将序列删空(例如 a = [ 1 , 2 , 2 , 1 ] a = [1, 2, 2, 1] a=[1,2,2,1])。

思路分析

  1. 数据结构
    • a[1..2n]:原始输入序列。
    • b[x]:数值 x 首次出现的位置,用于构建配对关系。
    • c[i]:位置 i 的配对位置(满足 a[i]==a[c[i]]c[c[i]]==i)。
    • q1, q2:双端队列,分别存储左段和右段的下标。
      • q1 保持原序,q2 逆序存储,便于同时从两端访问。
  2. 核心函数 slv(ch)
    • 第一步 :根据 ch 确定第一次操作方向,并找到配对位置 o = c[st]
    • 区间划分 :剩余下标分为左段 [l, r] 和右段 [l2, r2],分别按顺序推入 q1 和按逆序推入 q2
    • 贪心构造前半部分n 步):
      • 每一步从整体左端 (p) 或整体右端 (q) 取出一个下标,并移除其配对下标(位于 rs)。
      • 按字典序由小到大的顺序检查四种合法情况:
        1. 取左端,配对右段左端 → 当前操作 L,配对取出方向 R
        2. 取左端,配对左段右端 → 当前操作 L,配对取出方向 L
        3. 取右端,配对左段右端 → 当前操作 R,配对取出方向 L
        4. 取右端,配对右段左端 → 当前操作 R,配对取出方向 R
      • 若四种均不满足,当前方案无解。
    • 生成完整操作串
      • ans 记录前半部分的每一步操作(长度 n)。
      • sf 记录每一步配对元素被取出时的操作方向(长度 n,首字符固定 L 对应最后一步)。
      • sf 反转后拼接到 ans 后,得到长度为 2n 的完整操作序列。
  3. 字典序最小保证
    • 先尝试 'L' 作为第一步,失败后才尝试 'R'
    • 每一步优先尝试取 'L',且 'L' 时优先匹配右段左端,'R' 时优先匹配左段右端。
    • 最后一步固定为 'L'(剩余唯一元素时取左字典序更小)。
    • 上述贪心策略确保在所有可行方案中输出字典序最小的操作串。
  4. 复杂度
    • 每组数据仅需一次线性扫描建立配对关系,以及线性次的队列操作。
    • 总复杂度 O(∑n),在 ∑n ≤ 5×10^5 的限制下可轻松通过洛谷所有测试点。

代码实现

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

const int N = 1000005;
int T, n, m;
int a[N], b[N], c[N];   // a:原数组, b:值首次出现位置, c:配对位置

bool slv(char ch) {
    deque<int> q1, q2;   // q1:左段(原序), q2:右段(逆序)
    string ans, sf;      // ans:前半操作, sf:后半操作(待反转)
    ans = ch;
    sf = "L";            // 最后一步强制取左

    int o;               // 与第一次取出的数配对的位置
    if (ch == 'L') {
        o = c[1];
        for (int i = 2; i < o; ++i) q1.push_back(i);
        for (int i = n; i > o; --i) q2.push_back(i);
    } else {
        o = c[n];
        for (int i = 1; i < o; ++i) q1.push_back(i);
        for (int i = n - 1; i > o; --i) q2.push_back(i);
    }

    while (!q1.empty() || !q2.empty()) {
        // p:整体左端, q:整体右端, r:左段右端, s:右段左端
        int p = q1.empty() ? 0 : q1.front();
        int q = q2.empty() ? 0 : q2.front();
        int r = q1.empty() ? 0 : q1.back();
        int s = q2.empty() ? 0 : q2.back();

        // 四种可行情况,字典序依次减小
        if (p && c[p] == s) {      // 取左,配右段左端
            ans += 'L';
            sf += 'R';
            q1.pop_front();
            q2.pop_back();
        } else if (p && c[p] == r) { // 取左,配左段右端
            ans += 'L';
            sf += 'L';
            q1.pop_front();
            q1.pop_back();
        } else if (q && c[q] == r) { // 取右,配左段右端
            ans += 'R';
            sf += 'L';
            q2.pop_front();
            q1.pop_back();
        } else if (q && c[q] == s) { // 取右,配右段左端
            ans += 'R';
            sf += 'R';
            q2.pop_front();
            q2.pop_back();
        } else return false;
    }

    reverse(sf.begin(), sf.end());
    ans += sf;
    cout << ans << '\n';
    return true;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> T;
    while (T--) {
        cin >> m;
        n = m * 2;

        // 初始化位置数组
        for (int i = 1; i <= m; ++i) b[i] = 0;
        for (int i = 1; i <= n; ++i) {
            cin >> a[i];
            if (b[a[i]]) {
                c[b[a[i]]] = i;
                c[i] = b[a[i]];
            } else {
                b[a[i]] = i;
            }
        }

        // 先尝试第一步取左,再尝试第一步取右
        if (!slv('L') && !slv('R')) cout << "-1\n";
    }
    return 0;
}

专栏推荐:信奥赛C++提高组csp-s初赛&复赛真题题解(持续更新)
https://blog.csdn.net/weixin_66461496/category_13125089.html


各种学习资料,助力大家一站式学习和提升!!!

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int main(){
	cout<<"##########  一站式掌握信奥赛知识!  ##########";
	cout<<"#############  冲刺信奥赛拿奖!  #############";
	cout<<"######  课程购买后永久学习,不受限制!   ######";
	return 0;
}

1、csp信奥赛高频考点知识详解及案例实践:

CSP信奥赛C++动态规划:
https://blog.csdn.net/weixin_66461496/category_13096895.html点击跳转

CSP信奥赛C++标准模板库STL:
https://blog.csdn.net/weixin_66461496/category_13108077.html 点击跳转

信奥赛C++提高组csp-s知识详解及案例实践:
https://blog.csdn.net/weixin_66461496/category_13113932.html

2、csp信奥赛冲刺一等奖有效刷题题解:

CSP信奥赛C++初赛及复赛高频考点真题解析(持续更新):https://blog.csdn.net/weixin_66461496/category_12808781.html 点击跳转

CSP信奥赛C++一等奖通关刷题题单及题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12673810.html 点击跳转

信奥赛C++提高组csp-s初赛&复赛真题题解(持续更新)
https://blog.csdn.net/weixin_66461496/category_13125089.html

3、GESP C++考级真题题解:

GESP(C++ 一级+二级+三级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12858102.html 点击跳转

GESP(C++ 四级+五级+六级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12869848.html 点击跳转

GESP(C++ 七级+八级)真题题解(持续更新):
https://blog.csdn.net/weixin_66461496/category_13117178.html

4、CSP信奥赛C++竞赛拿奖视频课:

https://edu.csdn.net/course/detail/40437 点击跳转

· 文末祝福 ·

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int main(){
	cout<<"跟着王老师一起学习信奥赛C++";
	cout<<"    成就更好的自己!       ";
	cout<<"  csp信奥赛一等奖属于你!   ";
	return 0;
}
相关推荐
0 0 02 小时前
【C++】矩阵翻转/n*n的矩阵旋转
c++·线性代数·算法·矩阵
sycmancia2 小时前
C++——类的真正形态、构造函数的调用
开发语言·c++
CHANG_THE_WORLD2 小时前
C/C++字符串定义的五种写法 和 C/C++字符串隐藏技术深度剖析
c++
sycmancia2 小时前
C++——初始化列表的使用
开发语言·c++
白太岁2 小时前
Redis:(3) Lua 与 Redis、基于连接池的 Facade 模式封装
数据库·c++·redis·lua·外观模式
『往事』&白驹过隙;2 小时前
系统编程的内存零拷贝(Zero-Copy)技术
linux·c语言·网络·c++·物联网·iot
量子炒饭大师3 小时前
【C++入门】Cyber高维的蜂巢意识 —— 【类与对象】static 成员
开发语言·c++·静态成员变量·static成员
ShineWinsu3 小时前
对于stack和queue经典算法题目:155. 最小栈、JZ31 栈的压入、弹出序列和102. 二叉树的层序遍历的解析
数据结构·c++·算法·面试·力扣·笔试·牛客网
SWAGGY..3 小时前
【c++初阶】:(1)c++入门基础知识
开发语言·c++