[CSP-S 2021] 回文

news2024/11/19 23:16:23

[CSP-S 2021] 回文

题目描述:

给定正整数 n 和整数序列 a1​,a2​,…,a2n​,在这 2n 个数中,1,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。

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

字典序的比较规则如下:

长度均为 2n 的字符串 s1∼2n​ 比 t1∼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

输入 #2:

见附件中的 palin/palin2.in

输出 #2:

见附件中的 palin/palin2.ans

说明/提示

【样例解释 #1】

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

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

【数据范围】

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

对所有测试点保证 1≤T≤100,1≤n,∑n≤5×10^5。

测试点编号T≤n≤∑n≤特殊性质
1∼7101050
8∼10100201000
11∼121001001000
13∼15100100025000
16∼1715×10^55×10^5
18∼201005×10^55×10^5
21∼251005×10^55×10^5

特殊性质:

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

附件下载:

palin.zip 4.25KB

思路:

  这个题解思路清奇(就是暴力加上了一堆优化然后跑过了),相较于 std 可能会有所不同。

首先一看,回文,好耶, Hash —— 然而并不是。

众所周知,了解一道题的最好方法就是手推样例(瞎说的),所以我们来手推一下。

样例一的第一组数据的 a 和 b:

然后手模一下怎么生成 b 的:

 

嗯,连得密密麻麻的不想看,做 T4 去了。

这当然是不可以的,但是这样看确实看不出来什么东西。

想一下题上有什么提示……

b 是一个回文数列。

既然 b 是一个好观察的回文数列那就从 b 入手吧。

b 前后相等,那就把前后分开看看?

好像也没有什么。

不对,看一看后半段 b 。

这是由连续的一段 a 组成的。

想一想,不论什么 b 的后半段都是由 a 中一段连续的,长度为 n 的数列组成的。

就像这样:

(长度不准确请忽视)

有了这个,我们就可以开始我们优雅の暴力了。

1.暴力枚举每一个后半段区间位置。

2.判断是否合法。

3.输出。

然后这个时候有人就会跑上来说,你这个暴力是 O(n^3) 的啊不是 O(n^2) 的啊。

( n^3 : 枚举 n 个区间、记录每个区间 n 个数,判断是否合法要 n 次判断)

所以我们需要对暴力进行优化。

优化一:

首先我们想,一个后半段区间假若有机会成为解,那么必要条件是……

当然是这个区间里包含了 1,2,...,n 啦(不然你怎么回文)。

那么我们在枚举一个后半段区间时可以顺便来看看它是否包含了 1,2,...,n,没有则直接跳到下一个区间,否则来判断是否合法以及假如合法则方案是否是字典序最小的。

当然这个优化对于随机数据收益巨大,能大幅消减判断的 n 以至于能看成 O(n^2) ,因为减少了很多不必要的判断;但是对于构造的数据,比如说这个:

1,2,...,10000,1,2,...,10000

那这个优化就什么用都没有,直接退化 O(n^3)。

优化二

所以再来一个显而易见的优化:每个区间都是在上一个区间基础上挪了一个罢了,所以可以直接在上一个区间的基础上进行改动,这样就把记录每个区间的数的 n 就消掉了,这样就是一个完全的 O(n^2) 暴力了。加上前面的优化,对于随机数据能直接跑到近似于 O(n) 乘上一个大常数。

(民间数据能 AC ,不得不说真的有点水) 现在只有 80 分了悲伤

优化二代码:

#include<bits/stdc++.h>
using namespace std;
int t,n,tot,now,check,qrs;
int num[1000001],cnt;
int in[1000001];
char ans[1000001],maybeans[1000001];
stack<int>castle_3;
stack<int>lancet_2;
int main()
{
//	freopen("palin.in","r",stdin);
//	freopen("palin.out","w",stdout);
	cin>>t;
	while(t--)
	{
		memset(ans,0,sizeof(ans));
		memset(in,0,sizeof(in));
		cnt=0;
		qrs=0;
		cin>>n;
		for(int i=1;i<=2*n;i++)
		{
			cin>>num[i];
		}
		int l=1,r=n;
		for(int i=l;i<=r;i++)
		{
			in[num[i]]++;
			if(in[num[i]]==1)
			{
				cnt++;
			}
		}
		while(r<=2*n)
		{
			if(cnt==n)
			{
				memset(maybeans,0,sizeof(maybeans));
				check=0;
				for(int i=1;i<l;i++)
				{
					castle_3.push(num[i]);
				}
				for(int j=2*n;j>r;j--)
				{
					lancet_2.push(num[j]);
				}
				int ll=l,rr=r;
				int a,b;
				int now=0;
				while(ll<=rr)
				{
					a=-1;
					b=-1;
					if(castle_3.size()) a=castle_3.top();
					if(lancet_2.size()) b=lancet_2.top();
					if(b==num[ll])
					{
						maybeans[n-now]='R';
						maybeans[n+1+now]='L';
						lancet_2.pop();
						now++;
						ll++;
					}
					else if(b==num[rr])
					{
						maybeans[n-now]='R';
						maybeans[n+1+now]='R';
						lancet_2.pop();
						now++;
						rr--;
					}
					else if(a==num[ll])
					{
						maybeans[n-now]='L';
						maybeans[n+1+now]='L';
						castle_3.pop();
						now++;
						ll++;
					}
					else if(a==num[rr])
					{
						maybeans[n-now]='L';
						maybeans[n+1+now]='R';
						castle_3.pop();
						now++;
						rr--;
					}
					else
					{
						check=1;
						break;
					}
				}
				if(!check)
				{
					if(!qrs)
					{
						for(int i=1;i<=n*2;i++)
						{
							ans[i]=maybeans[i];
						}
						qrs=1;
					}
					else
					{
						int i=1;
						int checks=0;
						for(;i<=n*2;i++)
						{
							if(ans[i]!=maybeans[i])
							{
								checks=1;
								break;
							}
						}
						if(maybeans[i]=='L'&&checks)
						{
							for(int j=1;j<=n*2;j++)
							{
								ans[j]=maybeans[j];
							}
						}
					}
				}
				while(!lancet_2.empty()) lancet_2.pop();
				while(!castle_3.empty()) castle_3.pop();
			}
			in[num[l]]--;
			if(!in[num[l]]) cnt--;
			l++;
			r++;
			in[num[r]]++;
			if(in[num[r]]==1) cnt++; 
		}
		if(!qrs)
		{
			cout<<-1<<endl;
		}
		else
		{
			for(int i=1;i<=n*2;i++)
			{
				cout<<ans[i];
			}
			cout<<endl;
		}
	}
}

判断可行性解释:用两个栈记录左边前半部分与右边前半部分,然后和后半部分尝试匹配。每匹配成功就把它们加入maybeans方案中。对于这个“匹配”,请好好读读,因为后半段优化是基于这个“匹配”的。

代码解释: in 数组是用来记录区间内所有数的个数, cnt 是用来记录区间内有多少个不同的数;对于 in 和 cnt 的变化规律为什么是这样,手推一遍就很容易明白。然后是让每个区间生成最优解:因为我们需要让操作 R 尽可能在后,那么我们就先让 前半部分的右端先进行匹配,这样对于每个枚举的后半段区间,生成的答案就是局部最优解

然后机房大佬 FJN 因为没有打出暴力 T3 感到非常悲伤,然后听说我打出了一个随机化数据能跑 O(n) ,构造能跑 O(n^2) 的暴力于是来找我看看代码。

看着看着然后他就来了一句:你这个可以再优化成 O(n) 的。

对,这就是最后一个优化。

优化三

概括一下就是:倒序枚举后半段区间,当找到第一个解时就一定是最优解。

为甚么呢?

首先我们想一个基础事实:假如 a 中有 2 个或以上的符合要求的后半段区间,那么它们一定是有重合部分的。

(例外: 1,2,3,1,2,3 中前一个 1,2,3 和后一个 1,2,3 不重合,但是看一眼就知道选后面一个答案更优。)

就像这样:

为什么会重合?因为两段区间都包含 1 至 n 中所有数,而 1 至 n 每个又只有两个,所以一定会重合。

上图的红绿线段就是两个符合要求的后半段区间,那么显而易见的可以知道,没有重叠的部分是相同的。(你问为什么? A-B=A-B )

那么,在进行回文配对时,红色没有重叠部分一定是跟绿色没有重叠部分进行配对。毕竟含有相同的数。

那么,假若我们选择红色作为后半段区间,那么绿色的未重叠部分则是用 R 操作弹出。(假如可行)

假若我们选择绿色作为后半段区间,那么红色的未重叠部分则是用 L 操作弹出。(假如可行)

回看匹配过程,不管我们选择红色还是绿色,我们一定是先将不属于红色或绿色的是跟红绿重合部分匹配(你不把它们匹配完怎么匹配中间的啊),那么它们的在方案中位置及方案是固定的,那么就剩红色和绿色不重叠部分了。

假如我们选择是红色且红色可行,那么在这里我们会全部用 R 进行剩余匹配。

假如是绿色且绿色可行,那么在这里我们会全部用 L 进行剩余匹配。

显而易见的,绿色更优。

所以两段后半部分区间都可行时,那么选择后面那段作为后半区间更优。

于是乎,倒着枚举区间,遇到第一个可行的区间就输出答案,正确性证明完毕,于是就开心滴砍掉了大部分判断这个 n 。结合前面的优化,一个近似于 O(n) 就这么搞出来啦~

当然,如果真的要专门构造数据来卡当然是可以退化到 O(n^2) 的(悲)

完整代码:

#include<bits/stdc++.h>
#define ooffof 1000001
using namespace std;
int t,n,tot,now,check,qrs;
int num[ooffof],cnt;
int in[ooffof];
char ans[ooffof],maybeans[ooffof];
int l[ooffof],r[ooffof],klk=0;;
int main()
{
//	freopen("palin.in","r",stdin);
//	freopen("palin.out","w",stdout);
	cin>>t;
	while(t--)
	{
		memset(ans,0,sizeof(ans));
		memset(in,0,sizeof(in));
		cnt=0;
		qrs=0;
		klk=0;
		cin>>n;
		for(int i=1;i<=2*n;i++)
		{
			cin>>num[i];
			in[num[i]]++;
			if(in[num[i]]==1)
			{
				cnt++;
			}
			if(i>n)
			{
				in[num[i-n]]--;
				if(in[num[i-n]]==0)
				{
					cnt--;
				}
			}
			if(cnt==n)
			{
				l[++klk]=i-n+1;
				r[klk]=i;
			}
		}
		for(int q=klk;q>=1;q--)
		{
			memset(maybeans,0,sizeof(maybeans));
			check=0;
			int ll=l[q],rr=r[q],sl=l[q]-1,rl=r[q]+1;
			int a,b;
			int now=0;
			while(ll<=rr)
			{	
				a=-1;
				b=-1;
				if(sl>=1) a=num[sl];
				if(rl<=2*n) b=num[rl];	
				if(b==num[ll])
				{
					maybeans[n-now]='R';
					maybeans[n+1+now]='L';
					rl++;
					now++;
					ll++;
				}
				else if(b==num[rr])
				{
					maybeans[n-now]='R';
					maybeans[n+1+now]='R';
					rl++;
					now++;
					rr--;
				}
				else if(a==num[ll])
				{
					maybeans[n-now]='L';
					maybeans[n+1+now]='L';
					sl--;
					now++;
					ll++;
				}
				else if(a==num[rr])
				{
					maybeans[n-now]='L';
					maybeans[n+1+now]='R';
					sl--;
					now++;
					rr--;
				}
				else
				{
					check=1;
					break;
				}
			}
			if(!check)
			{
				for(int i=1;i<=n*2;i++)
				{
					cout<<maybeans[i];
					qrs=1;
				}
				cout<<endl;
			}
			if(qrs)
			{
				break;
			}
		}
		if(!qrs)
		{
			cout<<-1<<endl;
		}
	}
}

总结:

比 T2 简单的 T3 这辈子不多了……

是名副其实的签到题……

 不过能从暴力n^3优化到n,这是一个非常难的过程。

题目链接:

[CSP-S 2021] 回文 - 洛谷https://www.luogu.com.cn/problem/P7915 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/653643.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【SpringCloud入门】-- SpringCloud优质组件介绍

目录 1. SpringCloud优质项目 2. 介绍SpringCloud优质项目 SpringCloudConfig(Spring) SpringCloudBus Eureka Hystrix Zuul Archaius Consul SpringCloudForCloudFoundry SpringCloudSleuth SpringCloudDataFlow SpringCloudSecurity SpringCloudZookeeper Spr…

【Redis】孔夫子旧书网爬虫接入芝麻代理IP:代理IP利用效率最大化

背景&#xff1a; 之前用过芝麻IP&#xff0c;写过这几篇文章 《【Python】芝麻HTTP代理系列保姆级全套攻略(对接教程自动领取每日IPIP最优算法)》 《【Python】记录抓包分析自动领取芝麻HTTP每日免费IP&#xff08;成品教程&#xff09;》 《爬虫增加代理池&#xff1a;使用稳…

ICC2:自定义快捷键和菜单

把一些常用的功能放在一个菜单里是什么体验?直接放在工具栏里是不是更方便?那设置成快捷键呢? gui_create_menu 自定义菜单可以把工具常用的功能放到一个菜单里,用户也可以把“执行脚本操作”加到菜单里。 举例来说: 1)把Editor Toolbox放到Favorite菜单里,floorplan 操…

行业报告 | AIGC发展研究

原创 | 文 BFT机器人 01 技术篇 深度学习进化史:知识变轨 风起云涌 已发生的关键步骤&#xff1a; 人工神经网络的诞生 反向传播算法的提出 GPU的使用 大数据的出现 预训练和迁移学习 生成对抗网络 (GAN) 的发明 强化学习的成功应用 自然语言处理的突破 即将发生的关键…

MinGW-w64安装和使用_亲测有效

MinGW-w64 是什么&#xff01;&#xff1f; MinGW-w64 是一个在 Windows 系统上运行的 GNU 编译器套件&#xff0c;支持 C 和 C 语言的编译。它包括了 GCC 编译器、GNU Binutils 和一些其他的工具。在 MinGW-w64 中 各个版本的参数含义如下&#xff1a; x86_64&#xff1a;表…

1.ORB-SLAM3系统概述

1.内容简介 本系列文章主要基于ORB-SLAM3代码、论文以及相关博客&#xff0c;对算法原理进行总结和梳理。 ORB-SLAM系列整体架构是不变的&#xff0c;都包含Tracking、LocalMapping和LoopClosing三个核心线程&#xff0c;中间伴随着优化过程。在ORB-SLAM3算法中比较突出的改进…

腾讯安全董志强:四大关键步骤促进数据安全治理闭环,提升企业免疫力

高速发展的数字时代&#xff0c;数据已成为推动产业发展的最重要生产要素之一&#xff0c;真正成为了创造经济财富的数字能源&#xff0c;守护数据资产的安全成为企业高质量发展不可回避的重要命题。 6月13日&#xff0c;腾讯安全联合IDC发布“数字安全免疫力”模型框架&#…

我被一家无货源电商培训公司骗了怎么办?

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 最近&#xff0c;一位被无货源电商培训骗的人找到了卢松松&#xff0c;她说&#xff1a; 老师&#xff0c;你好&#xff0c;我是被无货源电商课程骗了的受害人&#xff0c;走投无路了&#xff0c;想…

5个超好用的开源工具库分享~

在实际项目开发中&#xff0c;从稳定性和效率的角度考虑&#xff0c;重复造轮子是不被提倡的。但是&#xff0c;自己在学习过程中造轮子绝对是对自己百利而无一害的&#xff0c;造轮子是一种特别能够提高自己系统编程能力的手段。 今天分享几个我常用的开源工具库&#xff1a;…

大佬们都是如何编写测试方案的?

目录 1、背景 2、编写的方式 2.1 第一阶段&#xff1a;在需求评审开始前 2.2 第二阶段&#xff1a;在需求评审开始后&#xff0c;技术方案设计中 2.3 第三阶段&#xff1a;技术方案设计后 2.4 第四阶段&#xff1a;测试方案评审前 2.5 第五阶段&#xff1a;测试方案评审…

Opencv-C++笔记 (7) : opencv-文件操作XML和YMAL文件

文章目录 一、概述二、文件操作三、打开文件四、写入五、读写个人源码 一、概述 除了图像数据之外&#xff0c;有时程序中的尺寸较小的Mat类矩阵、字符串、数组等 数据也需要进行保存&#xff0c;这些数据通常保存成XML文件或者YAML文件。本小节中将介绍如何利用OpenCV 4中的函…

前端实现消息推送、即时通信、http简介

信息推送 服务端主动向客户端推送消息&#xff0c;使客户端能够即时接收到信息。 场景 页面接收到点赞&#xff0c;消息提醒聊天功能弹幕功能实时更新数据功能 实现即时通讯方式 短轮询 浏览器&#xff08;客户端&#xff09;每隔一段时间向服务器发送http请求&#xff0c;…

Google为TensorFlow设计的专用集成电路TPU3.0图片

Widrow也是在Minsky的影响下进入AI领域的&#xff0c;后来加入斯坦福大学任教。他在1960年提出了自适应线性单元&#xff08;Adaline&#xff09;&#xff0c;一种和感知器类似的单层神经网络&#xff0c;用求导数方法来调整权重&#xff0c;所以说有“三十年神经网络经验”并不…

CI/CD 流水线 (FREE)

流水线是持续集成、交付和部署的顶级组件。 流水线包括&#xff1a; 工作&#xff0c;定义做什么。例如&#xff0c;编译或测试代码的作业。阶段&#xff0c;定义何时运行作业。例如&#xff0c;在编译代码的阶段之后运行测试的阶段。 作业由 runners 执行。如果有足够多的并…

Qt编写视频监控系统79-四种界面导航栏的设计

一、前言 最初视频监控系统按照二级菜单的设计思路&#xff0c;顶部标题栏一级菜单&#xff0c;左侧对应二级菜单&#xff0c;最初采用图片在上面&#xff0c;文字在下面的按钮方式展示&#xff0c;随着功能的增加&#xff0c;二级菜单越来越多&#xff0c;如果都是这个图文上…

openGauss数据库安装,配置连接 完整版Centos7

服务器版本&#xff1a;Centos7.6 || 7.9 数据库版本&#xff1a;openGauss-5.0.0-CentOS-64bit.tar.bz2 极简版 目录 修改系统参数安装环境安装openGauss数据库配置连接数据库使用navicat连接数据库 修改系统参数 ##修改 /etc/selinux/config 文件中的“SELINUX”值为“disa…

【网络安全】成功上岸深信服,这套面试题你肯定需要!!!

时间过得很快&#xff0c;回想起去年的这个时候&#xff0c;我也正在准备秋招&#xff0c;今天的我刚刚结束培训。 我的个人情况就读于某双非大学&#xff0c;信息与计算科学&#xff08;大数据方向&#xff0c;校企合作&#xff0c;一个介于数学与计算机之间的专业&#xff0…

移动端H5使用window.open跳转,IOS不生效解决

移动端H5使用window.open跳转&#xff0c;IOS不生效解决 navigator navigator对象&#xff0c;用于提供当前浏览器及操作系统等信息&#xff0c;这些信息都放在navigator的各个属性中。navigator对象也是window对象的成员。 打印navigator对象 userAgent在安卓和IOS的打印结…

Opencv-C++笔记 (9) : opencv-多通道分离和合并

文章目录 一、概论二、多通道分离函数split()三、多通道合并函数merge()四、图像多通道分离与合并例程 一、概论 在图像颜色模型中不同的分量存放在不同的通道中&#xff0c;如果我们只需要颜色模型的某一个分量&#xff0c;例如只需要处理RGB图像中的红色通道&#xff0c;可以…

科技云报道:大模型时代,SaaS元年才真的到来了?

科技云报道原创。 ChatGPT席卷全球后&#xff0c;如果有人问AI大模型影响最大的会是哪个行业&#xff1f;SaaS领域肯定是不二之选。 目前全球各大科技公司已宣称要用大模型触及、整合所有产品。 其中&#xff0c;微软率先为其办公家族装配上了各类copilot&#xff0c;开发者…