回文(CSP-S-2021-palin)题解

本文介绍了蓝桥杯竞赛中关于回文数列构造的问题,详细解析了题意,并提供了字典序最小操作方案的思路。通过内外同时开工的策略,判断并填充序列,确保回文数列的形成。同时,文章还展示了两种不同的代码实现,以帮助理解解题过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

老规矩,献上题目:


题目描述

给定正整数 n 和整数序列 a1​,a2​,…,a2n​,在这 2 n2n 个数中, n1,2,…,n 分别各出现恰好 2 次。现在进行 2n 次操作,目标是创建一个长度同样为 2n 的序列 b1​,b2​,…,b2n​,初始时 b 为空序列,每次可以进行以下两种操作之一:

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

我们的目的是让 b 成为一个回文数列,即令其满足对所有 1≤i≤n,有 bi​=b2n+1−i​。请你判断该目的是否能达成,如果可以,请输出字典序最小的操作方案

输入格式

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

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

第一行,包含一个正整数 n。
第二行,包含 2n 个用空格隔开的整数 a1​,a2​,…,a2n​。

输出格式

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

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

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

字典序的比较规则如下:长度均为 2 n 的字符串 s 1∼2n​ 比t 1∼2n​ 字典序小,当且仅当存在下标1≤k≤2n 使得对于每个 1≤i<k 有si​=ti​ 且 sk​<tk​。

输入输出样例

输入 #1

2
5
4 1 2 4 5 3 1 2 3 5
3
3 2 1 2 1 3

输出 #1

LRRLLRRRRL
-1


题解:

根据题意:

我们首先要明确一点:能选左边就尽量不要选右边,因为题目要求输出字典序最小的一组答案,L肯定优先于R;

其次,如果能构成b数组的话,那么输出的答案的最后一个字符一定是'L',这个不用我多说,因为选到最后只有一个字符,我们既可以把它当作左边,也可以把它当作右边。那么我们肯定要选左边啊,这样尽可能满足字典序小。

对待这道题,我们依然有2种方法:

骨灰级做法:枚举出所有可能的情况(O(2^N)),不用我说你也知道肯定要TEL掉的,我们对这种无脑方法一概而过

妙哉:我们可以内外同时开工:

最先开始,我们有两种情况——选最左边或者是最右边,我们优先选择最左边,如果左边不行再考虑右边。

我们以左边为例展开讨论(右边和左边的思路一模一样,不再累述)>>>>

给定一个长度为2n的序列N,建立数组S表示我们的答案,由’‘L’R‘组成;序列最左边记waileft,数组最右边记为wairight

初始状态下,外层waileft下标为1,wairight下标为2n

我们设最左边的数为X,取出X;然后在剩下的序列中找出与X相等的值,记作它们的下标分别为 i1,i2,记i2左边的数为neileft,i2右边的数记为neiright

取出X后,外层left下标变为i1+1,右边right不变还是2n

接下来我们就要两边同时开工了,怎么开工呢?

上述操作后,我们已经找出与X相等值的下标。由于我们最终得到的是回文序列,那么这个回文序列无论是从左到右读还是从右到左读,顺序都是一样的,容易知道,最终回文序列的两端都是X,那么回文序列临近X的一组相等的值肯定在原序列中一个在外层的最左边waileft或最右边wairight,一个在下标为i2的左边或右边(neileft or neiright)

那好了,有四种情况:

1.        N[waileft]=N[neileft]

2.        N[waileft]=N[neiright]

3.        N[wairight]=N[neileft]

4         N[wairight]=N[neiright]

注意:这四种情况的顺序我可不是随意写的,它必须是严谨的。为什么这么说呢?

因为上述的四种情况就是4种操作:

满足对应的情况,我们就进行相应的操作

1.            L L

2.            L R

3.             R L

4.             R R

我们还是要遵循字典序最小的原则嘛

那要是四种情况都不满足呢?那就说明这种情况不成立。我们要重新选择最右边再来,如果也不成立,那就说明这个序列不能形成回文,果断输出-1,结束程序。这是判断误解,减少冗余运算的关键一点!

这样我们判断一次就可以同时填补S数组的两端的一个值,不断向内填补空白。

如何填补?

最先开始选择左边那么S[1]=L  ,S[2n]=L

如果选择右边              S[1]=R  ,S[2n]=L

不管是外层还是内层,只要选择左边(waileft or neileft),我们都记作L;

只要选择右边(wairight or neiright),我们都记作R

分别由两边向内填补,这样如果能够填满,它一定是正确的答案。

完整的代码如下

Code(100 ptx)

#include<cstdio>
int num[1000005];
char s[1000005];
int tot,t;
int main(){
	scanf("%d",&tot);
	while(tot--){
		scanf("%d",&t);
		int sum=2*t,nei;
		for(int i=1;i<=sum;i++)
			scanf("%d",&num[i]);
		for(int i=2;i<=sum;i++){
			if(num[1]==num[i]){
				nei=i;
				break;
			}
	    }
	    int wai_left=2,wai_right=sum,nei_left=nei-1,nei_right=nei+1;
        int start=1,end=sum;
	    s[start]='L';s[end]='L';
	    while(1){
	    	if(start==end-1){
	    		s[sum+1]='\0';
	    		printf("%s\n",s+1);
	    		break;
			}
	        if(num[wai_left]==num[nei_left] && wai_left<nei_left){
	        	s[++start]='L';s[--end]='L';
	         	wai_left++;nei_left--;
			}
			else if(num[wai_left]==num[nei_right] ){
			 	s[++start]='L';s[--end]='R';
	         	wai_left++;nei_right++;
			}
			else if(num[wai_right]==num[nei_left]){
				s[++start]='R';s[--end]='L';
	         	wai_right--;nei_left--;
			}
			else if(num[wai_right]==num[nei_right]  &&  wai_right>nei_right){
				s[++start]='R';s[--end]='R';
				wai_right--;nei_right++;
			}
			else break;
		}	
		if(start==end-1)continue;
		for(int i=1;i<sum;i++){
			if(num[sum]==num[i]){
				nei=i;
				break;
			}
	    }
	    wai_left=1;wai_right=sum-1;nei_left=nei-1;nei_right=nei+1;
	    start=1,end=sum;
	    s[start]='R';s[end]='L';
	    while(1){
	    	if(start==end-1){
	    		s[sum+1]='\0';
	    		printf("%s\n",s+1);
	    		break;
			}
	        if(num[wai_left]==num[nei_left] && wai_left<nei_left){
	        	s[++start]='L';s[--end]='L';
	            wai_left++;nei_left--;
			}
			else if(num[wai_left]==num[nei_right]){
			 	s[++start]='L';s[--end]='R';
	         	wai_left++;nei_right++;
			}
			else if(num[wai_right]==num[nei_left]){
				s[++start]='R';s[--end]='L';
	         	wai_right--;nei_left--;
			}
			else if(num[wai_right]==num[nei_right] && wai_right>nei_right){
				s[++start]='R';s[--end]='R';
	         	wai_right--;nei_right++;
			}
			else{
				putchar('-');putchar('1');putchar('\n');
				break;
			}
		}			
	}
	return 0;
} 

程序测评结果如下:

但是我第一次提交时的代码如下:

Code(first) 

#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=500005;
int n;
int a[maxn*2];
char res[maxn*2];
bool work(int l1,int r1,int l2,int r2)
	{
	for(int i=1;i<n;i++)
		{
		if((l1<=r1)&&(((l2<=r2)&&(a[l1]==a[l2]))||((l1<r1)&&(a[l1]==a[r1]))))
			{
			if((l1<r1)&&(a[l1]==a[r1]))
				{
				l1++;
				r1--;
				res[i]='L';
				res[2*(n-1)-i+1]='L';
				}
			else
				{
				l1++;
				l2++;
				res[i]='L';
				res[2*(n-1)-i+1]='R';
				}
			}
		else if((l2<=r2)&&(((l1<=r1)&&(a[r2]==a[r1]))||((l2<r2)&&(a[l2]==a[r2]))))
			{
			if((l2<r2)&&(a[l2]==a[r2]))
				{
				l2++;
				r2--;
				res[i]='R';
				res[2*(n-1)-i+1]='R';
				}
			else
				{
				r1--;
				r2--;
				res[i]='R';
				res[2*(n-1)-i+1]='L';
				}
			}
		else
			{
			return false;
			}
		}
	return true;
	}
int main()
	{
	int t;
	cin>>t;
	while(t--)
		{
		cin>>n;
		for(int i=1;i<=2*n;i++)
			{
			scanf("%d",&a[i]);
			}
		int l1=0;
		int l2=0;
		for(int i=2;i<=2*n;i++)
			{
			if(a[i]==a[1])
				{
				l1=i;
				break;
				}
			}
		for(int i=1;i<2*n;i++)
			{
			if(a[i]==a[2*n])
				{
				l2=i;
				break;
				}
			}
		if(work(2,l1-1,l1+1,2*n))
			{
			printf("L%sL",res+1);
			cout<<endl;
			}
		else if(work(1,l2-1,l2+1,2*n-1))
			{
			printf("R%sL",res+1);
			cout<<endl;
			}
		else
			{
			cout<<"-1"<<endl;
			}
		}
	return 0;
	}

评测结果如下:

 坑了我半个多小时。读者可以比较两个代码的区别。只是多加了两行

s[sum+1]='\0';

否则有可能会把上一个结果的残余项输出出来,必须要一个结束符号!为你们敲响警钟了.....

End~~~~

若有不懂的地方,欢迎评论下方咨询。

写作不易,谢谢大家支持!

### 关于CSP-S 2021的相关题目及题解 #### 题目概述 CSP-S 2021 是中国计算机学会举办的全国青少年信息学奥林匹克竞赛的一部分,属于提高的比赛。该赛事考察选手的算法设计能力以及编程实现技巧。以下是部分经典题目及其对应的题解--- #### **回文** ##### 简要题意 给定字符串 `s` 和若干次操作,每次操作可以删除某个字符或将某两个相邻字符交换位置。求最少的操作次数使得整个字符串成为回文串[^1]。 ##### 解决方案 此问题可以通过动态规划解决。定义状态 `dp[i][j]` 表示将子串 `s[i..j]` 转化为回文所需的最小操作数。转移方程如下: - 如果 `s[i] == s[j]`,则无需额外操作:`dp[i][j] = dp[i+1][j-1]` - 否则,取两种可能中的较小值:`dp[i][j] = min(dp[i+1][j], dp[i][j-1]) + 1` 最终答案即为 `dp[1][n]`,其中 `n` 是字符串长度。 ```cpp #include <iostream> #include <cstring> using namespace std; const int MAXN = 1005; int dp[MAXN][MAXN]; string s; int main() { cin >> s; memset(dp, 0x3f, sizeof(dp)); int n = s.size(); for (int i = 0; i < n; ++i) dp[i][i] = 0; for (int len = 2; len <= n; ++len) { for (int i = 0; i + len - 1 < n; ++i) { int j = i + len - 1; if (s[i] == s[j]) dp[i][j] = dp[i+1][j-1]; else dp[i][j] = min(dp[i+1][j], dp[i][j-1]) + 1; } } cout << dp[0][n-1] << endl; } ``` --- #### **括号序列** ##### 简要题意 给定一个由左括号 `'('` 和右括号 `')'` 成的字符串,允许执行有限次翻转操作(即将某些位置上的括号替换为其相反形式),问至少需要多少次翻转才能使整个字符串合法[^4]。 ##### 解决方案 通过维护前缀和的方式记录当前未匹配的左括号数量,并利用贪心策略计算所需翻转次数。具体而言,当遇到无法配对的情况时立即进行一次翻转并更新计数器。 ```cpp #include <bits/stdc++.h> using namespace std; int main(){ string str; cin>>str; long long cnt=0,res=0; for(auto c:str){ if(c=='(')cnt++; else{ if(cnt>0)cnt--; else res++,cnt++; // Flip ')' } } cout<<res+(cnt/2); } ``` --- #### 总结与评价 根据过往参赛者的反馈,本次比赛整体难度适中,适合有一定基础的学生挑战自我[^2]。对于像《回文》这样的高分题,掌握核心概念即可轻松得分;而对于稍复杂的DP类问题,则需多加练习以提升建模能力和代码调试效率。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

「已注销」

感谢有你陪伴

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值