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

老规矩,献上题目:


题目描述

给定正整数 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

输入输出样例

输入 #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
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_leftnei_right){
				s[++start]='R';s[--end]='R';
				wai_right--;nei_right++;
			}
			else break;
		}	
		if(start==end-1)continue;
		for(int i=1;inei_right){
				s[++start]='R';s[--end]='R';
	         	wai_right--;nei_right++;
			}
			else{
				putchar('-');putchar('1');putchar('\n');
				break;
			}
		}			
	}
	return 0;
} 

程序测评结果如下:

回文(CSP-S-2021-palin)题解_第1张图片

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

Code(first) 

#include
#include
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>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<

评测结果如下:

回文(CSP-S-2021-palin)题解_第2张图片

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

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

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

End~~~~

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

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

你可能感兴趣的:(编程,信息学竞赛,竞赛题解库,蓝桥杯,p2p,c++,职场和发展,c语言)