P7915 [CSP-S 2021] 回文
题目描述
给定正整数 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 为空序列,每次可以进行以下两种操作之一:
- 将序列 a a a 的开头元素加到 b b b 的末尾,并从 a a a 中移除。
- 将序列 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 的、由字符 L
或 R
构成的字符串(不含空格),其中 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
输入输出样例 #2
输入 #2
见附件中的 palin/palin2.in
输出 #2
见附件中的 palin/palin2.ans
说明/提示
【样例解释 #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])。
【hack 数据提供】
@潜在了H2O下面。
解题思路
- 核心问题在于如何安排操作序列,使得 b b b 满足回文性质。关键观察如下:
- 回文约束: b b b 的首尾必须相同,次首尾必须相同,依此类推。因此,序列 a a a 中每个数字的两次- - 出现必须被分配到 b b b 的对称位置上。
- 操作影响:操作 L 或 R 决定了元素进入 b b b 的顺序,从而影响对称位置的配对。
- 字典序最小:优先选择 L 操作(因为 L < R),但需保证最终能形成回文。
算法采用贪心策略:
- 初始化配对位置:预处理序列 a a a,为每个位置 i i i 记录其配对位置 j j j(即 a i = a j a_i = a_j ai=aj 且 i ≠ j i \neq j i=j)。
- 尝试两种起始操作:由于字典序要求,优先尝试以 L 开头(取 a a a 的开头元素),如果失败再尝试- - 以 R 开头(取 a a a 的末尾元素)。
- 模拟匹配过程:基于起始操作,将剩余位置划分为两个队列(左队列和右队列),然后贪心地匹配位置对:
- 每次检查队列两端的四种可能匹配:左队首 vs 左队尾、左队首 vs 右队尾、右队首 vs 左队尾、右队首 vs 右队尾。
- 如果匹配成功(即两位置的数字相同),记录操作并更新队列;优先选择能产生 L 操作的匹配以保持字典序最小。
- 构建操作序列:匹配过程中记录前半部分操作(xian)和后半部分操作(ho),最终输出为 xian 顺序加上 ho 逆序。
算法正确性
- 回文保证:匹配过程确保每个数字的两次出现被分配到对称位置,从而 b b b 满足 b i = b 2 n + 1 − i b_i = b_{2n+1-i} bi=b2n+1−i。
- 字典序最小:优先尝试 L 开头,且在匹配中优先选择 L 操作(对应 xian 添加 L)。如果 L 开头可行,直接输出;否则尝试 R 开头,但需保证输出方案是所有可行方案中字典序最小的。
- 可行性判断:如果两种起始操作均失败,则输出 -1。
- 复杂度分析
- 时间复杂度:每组数据时间复杂度为 O ( n ) O(n) O(n)。预处理配对位置 O ( n ) O(n) O(n),匹配过程每个元素处理一次 O ( n ) O(n) O(n)。总时间复杂度 O ( ∑ n ) O(\sum n) O(∑n),满足数据范围 ∑ n ≤ 5 × 1 0 5 \sum n \le 5 \times 10^5 ∑n≤5×105。
- 空间复杂度: O ( n ) O(n) O(n),用于存储序列、配对位置和队列。
代码实现解析
- 以下是基于题目所给代码的关键步骤解释(代码已优化):
- 预处理配对:使用数组 zhiqianshu 记录数字首次出现的位置,duiyinweizhi[i] 存储位置 i i i 的配对位置。
- 起始操作尝试:调用 solve(‘L’) 优先尝试 L 开头;若失败,再调用 solve(‘R’)。
- 匹配模拟:
- 根据起始操作初始化左队列(剩余位置的开头部分)和右队列(剩余位置的末尾部分)。
- 循环检查队列两端的四种匹配:
- 若左队首与左队尾匹配,添加两个 L 操作(记录到 xian 和 ho)。
- 若左队首与右队尾匹配,添加 L(xian)和 R(ho)。
- 若右队首与左队尾匹配,添加 R(xian)和 L(ho)。
- 若右队首与右队尾匹配,添加两个 R 操作(记录到 xian 和 ho)。
- 若无法匹配,返回失败。
- 输出操作序列:顺序输出 xian 中的操作,逆序输出 ho 中的操作(确保整体操作序列正确)。
示例分析
-
以样例输入 n = 5 n=5 n=5, a = [ 4 , 1 , 2 , 4 , 5 , 3 , 1 , 2 , 3 , 5 ] a = [4, 1, 2, 4, 5, 3, 1, 2, 3, 5] a=[4,1,2,4,5,3,1,2,3,5] 为例:
-
预处理配对:位置 1 1 1 和 4 4 4 均为 4 4 4,位置 2 2 2 和 7 7 7 均为 1 1 1,依此类推。
-
起始操作 L:取 a [ 1 ] = 4 a[1] = 4 a[1]=4,配对位置为 4 4 4。剩余位置划分为左队列 [ 2 , 3 ] [2, 3] [2,3]、右队列 [ 10 , 9 , 8 , 7 , 6 , 5 ] [10, 9, 8, 7, 6, 5] [10,9,8,7,6,5]。
-
匹配过程:
-
左队首 2 2 2( a [ 2 ] = 1 a[2]=1 a[2]=1)与右队尾 5 5 5( a [ 5 ] = 5 a[5]=5 a[5]=5)不匹配;但左队首 2 2 2 与右队尾 7 7 7( a [ 7 ] = 1 a[7]=1 a[7]=1)匹配,添加 L(xian)和 R(ho)。
-
更新队列后,继续匹配,最终得到 xian = [‘L’, ‘R’, ‘R’, ‘L’, ‘L’],ho = [‘R’, ‘R’, ‘R’, ‘R’, ‘L’]。
-
输出序列:xian 顺序输出 “LRRLL”,ho 逆序输出 “RRRRL”(即 “LRRLL” + “RRRRL” = “LRRLLRRRRL”)。
-
对于 n = 3 n=3 n=3, a = [ 3 , 2 , 1 , 2 , 1 , 3 ] a = [3, 2, 1, 2, 1, 3] a=[3,2,1,2,1,3]:
-
尝试 L 开头(取 a [ 1 ] = 3 a[1]=3 a[1]=3,配对位置 6 6 6),但剩余位置无法完成匹配。
-
尝试 R 开头(取 a [ 6 ] = 3 a[6]=3 a[6]=3,配对位置 1 1 1),剩余位置仍无法匹配,输出 -1。
详细代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+5;
int a[N*2],zhiqianshu[N*2],duiyinweizhi[N*2],n;
bool solve(char cc)
{deque<int>zuokuohao,youkuohao;vector<int>xian,ho;xian.push_back(cc=='L'?0:6);ho.push_back(0);if (cc=='L'){for(int i=2;i<duiyinweizhi[1];i++) zuokuohao.push_back(i);for(int i=n;i>duiyinweizhi[1];i--) youkuohao.push_back(i);} else{for(int i=1;i<duiyinweizhi[n];i++) zuokuohao.push_back(i);for(int i=n-1;i>duiyinweizhi[n];i--) youkuohao.push_back(i);}while(zuokuohao.size()>0||youkuohao.size()>0){int x1=zuokuohao.size()?zuokuohao.front():0;int x2=youkuohao.size()?youkuohao.front():0;int y1=zuokuohao.size()?zuokuohao.back():0;int y2=youkuohao.size()?youkuohao.back():0;if(duiyinweizhi[x1]==y1){xian.push_back(0);ho.push_back(0);zuokuohao.pop_front();zuokuohao.pop_back();}else if(duiyinweizhi[x1]==y2){xian.push_back(0);ho.push_back(6);zuokuohao.pop_front();youkuohao.pop_back();}else if(duiyinweizhi[x2]==y1){xian.push_back(6);ho.push_back(0);youkuohao.pop_front();zuokuohao.pop_back();}else if(duiyinweizhi[x2]==y2) {xian.push_back(6);ho.push_back(6);youkuohao.pop_front();youkuohao.pop_back();} elsereturn false;}for(vector<int>::iterator it=xian.begin();it!=xian.end();it++)cout<<char('L'+*it);for(vector<int>::reverse_iterator it=ho.rbegin();it!=ho.rend();it++)cout<<char('L'+*it);cout<<'\n';return true;
}
signed main()
{ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);int _;cin>>_;while (_--){cin>>n;memset(zhiqianshu,0,sizeof(zhiqianshu));n*=2;duiyinweizhi[0]=-1;for (int i=1;i<=n;i++){cin>>a[i];if(zhiqianshu[a[i]]){duiyinweizhi[zhiqianshu[a[i]]]=i;duiyinweizhi[i]=zhiqianshu[a[i]];}elsezhiqianshu[a[i]]=i;}if(!solve('L')&&!solve('R'))cout<<"-1"<<'\n';}return 0;
}