P7915 [CSP-S 2021] 回文 题解

赛时花了两个半小时在它身上。。

考场做法,自我感觉这道题推出来以后代码实现并不难

写篇题解纪念我 C S P   2021   J / S CSP\ 2021\ J/S CSP 2021 J/S 中唯二 100   p t s 100\ pts 100 pts 的题目(另一道是分糖果)。

思路

用样例 1 的第一个测试点来举例说明。

1

出队方向。

容易发现,当我们决定了第一个数字是从左右哪边出来的,每一个数字的出队方式就已经确定好了

样例中:

假如第一次从左边出,那么每一个数字的出队情况如下所示:

4 1 2 4 5 3 1 2 3 5
L L L L R R R R R R

原因:

既然我们先让最左边的 4 出队了,那么排在第 4 位(从左往右)的第二个 4 就必须是最后一个出去(因为要形成回文)。此时,这个 4 右边的 5 如果仍然是从左边出去,证明 5 右边的 4 已经从左边出去了,与前面“第二个 4 必须是最后一个出去的”相矛盾,说明这个 5 必须得从右边出去。

其他的数同理,可确定下来出队方向。

又为了保证字典序最小,所以我们要让第二个 4 从左边出(它是最后一个出队的,它无论左右出队都不影响)。

这样一来,我们只要确定第一次的出队方向,其余所有的数的出队方向都可以确定。


2

出队顺序。

本人认为这是这题比较难的点。

样例中:

我们先让队列最左边的 4 出队,那么就有两个数的出队顺序确定了。

4 1 2 4  5 3 1 2 3 5
1 \ \ 10 \ \ \ \ \ \ 

(按出队顺序先后从 1 1 1 n × 2 n \times 2 n×2 编号。)

如何确定下一个出队的数?

按字典序最小的原则,我们让 1 先出队。此时:

4 1 2 4  5 3 1 2 3 5
1 2 \ 10 \ \ 9 \ \ \

这样,按照我们 1 中已推出的结论,就会发现被 4 和 1 夹在中间的 5 和 3 没法出队了。

那换个方向,让 5 出队:

4 1 2 4  5 3 1 2 3 5
1 \ \ 10 9 \ \ \ \ 2

此时最后两个出队的 4 和 5 是相邻的,不会造成矛盾或无法出队的情况。

综上,在求出队顺序时,我们把最后出队的、相邻的、在队列中间的几个数看成一个“块”。

每次只有队头(或队尾)即将要出队的数的“另一个自己”紧贴着这个块(即在这个块的左边一位或者右边一位),这个数才可以出队。

不然会造成中间夹着数无法出队的情况。


这样以来,我们可以确定好每个数的出队方向和出队顺序,这道题的思路就到此结束了。

还不理解的可以拿样例手推一下,很容易可以想通。

代码实现

这道题真的挺考验选手的代码实现能力的。

我们在输入的时候记录下每一个数字出现的两个位置。按下标大小记录好,后面用的时候很容易搞混。

按照字典序尽量小的原理,我们优先让最左边的数出队。

维护两对指针,一对记录输入队列的头尾,一对记录“块”的左右下标。

每一次看队头或队尾的“第二位置”是否在块的相邻地方,是就出队。

如果队头队尾都无法出队,就失败了,如果最开始是从左边出的,就试试从右边出;如果最开始已经是从右边出去的了,就只能输出 -1 了。

输出结果的话,按照出队顺序排个序,再把出队方向输出即可。

这样一来,这道题就做完了 (切掉了)~

代码

考场代码

真的臭,勉强能看吧。

#include<bits/stdc++.h>
using namespace std;

#define rint register int
const int maxn = 1e6 + 5;
int T, n;
int a[maxn];
bool c[maxn >> 1];
struct node{
	int fx, nm;
}e[maxn];
struct node2{
	int fr, sc;
}b[maxn >> 1];
int l, r;
int h, t, flg;

inline void init ()
{
	memset (e, 0, sizeof e);
	memset (b, 0, sizeof b);
	memset (c, 0, sizeof c);
	l = r = h = t = flg = 0;
}

inline int read ()
{
	int s = 0, x = 1;
	char ch = getchar ();
	while (ch < '0' or ch > '9')
	{
		if (ch == '-')
			x = -1;
		ch = getchar ();	
	}	
	while (ch >= '0' and ch <= '9')
		s = s * 10 + ch - '0', ch = getchar ();
	return s * x;
}

inline void dl ()
{
	int cnt = 1;
	while (cnt < n and h <= t)
	{
		if (b[a[h]].sc == l - 1 and !e[b[a[h]].sc].nm)
		{
			e[h].fx = 1, e[h].nm = ++cnt;
			e[b[a[h]].sc].fx = 1, e[b[a[h]].sc].nm = 2 * n - cnt + 1;
			l -= 1, h += 1;
		}
		else if (b[a[h]].sc == r + 1 and !e[b[a[h]].sc].nm)
		{
			e[h].fx = 1, e[h].nm = ++cnt;
			e[b[a[h]].sc].fx = 2, e[b[a[h]].sc].nm = 2 * n - cnt + 1;
			r += 1, h += 1;
		}
		else if (b[a[t]].fr == l - 1 and !e[b[a[t]].fr].nm)
		{
			e[t].fx = 2, e[t].nm = ++cnt;
			e[b[a[t]].fr].fx = 1, e[b[a[t]].fr].nm = 2 * n - cnt + 1;
			l -= 1, t -= 1;
		}
		else if (b[a[t]].fr == r + 1 and !e[b[a[t]].fr].nm)
		{
			e[t].fx = 2, e[t].nm = ++cnt;
			e[b[a[t]].fr].fx = 2, e[b[a[t]].fr].nm = 2 * n - cnt + 1;
			r += 1, t -= 1;
		}
		else 
		{
			flg = 1;
			break;
		}
	}
}

inline bool cmp (node x, node y)
{
	return x.nm < y.nm;
}

inline void otpt ()
{
	sort (e + 1, e + (n << 1) + 1, cmp);
	for (rint i (1); i <= (n << 1); ++i)
	{
		if (e[i].fx == 1)
			printf ("L");
		else if (e[i].fx == 2)
			printf ("R");
	}
	printf ("\n");
} 

int main ()
{
//	freopen ("palin.in", "r", stdin); freopen ("palin.out", "w", stdout);
	T = read ();
	while (T--)
	{
		init ();
		n = read ();
		for (rint i (1); i <= (n << 1); ++i)
		{
			a[i] = read ();
			if (!c[a[i]])
				b[a[i]].fr = i, c[a[i]] = 1;
			else b[a[i]].sc = i;
		}
		
		//left begin
		e[1].fx = 1, e[1].nm = 1;
		e[b[a[1]].sc].fx = 1, e[b[a[1]].sc].nm = 2 * n;
		l = r = b[a[1]].sc;
		h = 2, t = 2 * n;
		flg = 0;
		dl ();
		if (!flg)
		{
			otpt ();
			continue;
		}
		
		//right begin
		memset (e, 0, sizeof e);
		e[n << 1].fx = 2, e[n << 1].nm = 1;
		e[b[a[n << 1]].fr].fx = 1, e[b[a[n << 1]].fr].nm = 2 * n;
		l = r = b[a[n << 1]].fr;
		h = 1, t = 2 * n - 1;
		flg = 0;
		dl ();
		if (!flg)
		{
			otpt ();
			continue;
		}
		else printf ("-1\n");
	}
	return 0;
	//-//
}

AC 代码

压了一下行,加了一些注释,好看了些。

#include<bits/stdc++.h>
using namespace std;

#define rint register int
const int maxn = 1e6 + 5;
int T, n, a[maxn];
bool c[maxn >> 1];
struct node{
	int fx, nm;//出队方向、出队顺序的编号 
}e[maxn];
struct node2{
	int fr, sc;//第一次出现的下标,第二次出现的下标 
}b[maxn >> 1];
int l, r, h, t, flg;

inline void init ()
{
	memset (e, 0, sizeof e);
	memset (b, 0, sizeof b);
	memset (c, 0, sizeof c);
	l = r = h = t = flg = 0;
}

inline int read ()
{
	int s = 0, x = 1;
	char ch = getchar ();
	while (ch < '0' or ch > '9'){if (ch == '-') x = -1; ch = getchar ();	}	
	while (ch >= '0' and ch <= '9') s = s * 10 + ch - '0', ch = getchar ();
	return s * x;
}

inline void dl ()
{
	int cnt = 1;//记录已经处理了的数的数量 
	while (cnt < n and h <= t)
	{
		if (b[a[h]].sc == l - 1 and !e[b[a[h]].sc].nm)//注意 if 条件的先后顺序,要保证字典序最小 
		{
			e[h].fx = 1, e[h].nm = ++cnt;
			e[b[a[h]].sc].fx = 1, e[b[a[h]].sc].nm = 2 * n - cnt + 1;
			l -= 1, h += 1;
		}
		else if (b[a[h]].sc == r + 1 and !e[b[a[h]].sc].nm)
		{
			e[h].fx = 1, e[h].nm = ++cnt;
			e[b[a[h]].sc].fx = 2, e[b[a[h]].sc].nm = 2 * n - cnt + 1;
			r += 1, h += 1;
		}
		else if (b[a[t]].fr == l - 1 and !e[b[a[t]].fr].nm)
		{
			e[t].fx = 2, e[t].nm = ++cnt;
			e[b[a[t]].fr].fx = 1, e[b[a[t]].fr].nm = 2 * n - cnt + 1;
			l -= 1, t -= 1;
		}
		else if (b[a[t]].fr == r + 1 and !e[b[a[t]].fr].nm)
		{
			e[t].fx = 2, e[t].nm = ++cnt;
			e[b[a[t]].fr].fx = 2, e[b[a[t]].fr].nm = 2 * n - cnt + 1;
			r += 1, t -= 1;
		}
		else 
		{
			flg = 1;
			break;
		}
	}
}

inline bool cmp (node x, node y)
{
	return x.nm < y.nm; 
}

inline void otpt ()//输出 
{
	sort (e + 1, e + (n << 1) + 1, cmp);
	for (rint i (1); i <= (n << 1); ++i)
		if (e[i].fx == 1) printf ("L");
		else if (e[i].fx == 2) printf ("R");
	printf ("\n");
} 

int main ()
{
	T = read ();
	while (T--)
	{
		init ();
		n = read ();
		for (rint i (1); i <= (n << 1); ++i)
		{
			a[i] = read ();
			if (!c[a[i]])
				b[a[i]].fr = i, c[a[i]] = 1;
			else b[a[i]].sc = i;//保证 fr 小于 sc 
		}
		//left begin
		e[1].fx = 1, e[1].nm = 1;
		e[b[a[1]].sc].fx = 1, e[b[a[1]].sc].nm = 2 * n;
		l = r = b[a[1]].sc;
		h = 2, t = 2 * n;
		flg = 0, dl ();//这里的 flag 也要清空,考场这个看了十几分钟 
		if (!flg)
		{
			otpt ();
			continue;
		}
		//right begin
		memset (e, 0, sizeof e);//一定要记得初始化 
		e[n << 1].fx = 2, e[n << 1].nm = 1;
		e[b[a[n << 1]].fr].fx = 1, e[b[a[n << 1]].fr].nm = 2 * n;//注意 fr 和 sc  
		l = r = b[a[n << 1]].fr;
		h = 1, t = 2 * n - 1;
		flg = 0, dl ();
		if (!flg)
		{
			otpt ();
			continue;
		}
		else printf ("-1\n");
	}
	return 0;
}

期中考 RP++~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
根据提供的引用内容,CSP-J2021复赛有两道题目,分别是分糖果和小熊的果篮。 对于第一题分糖果,题目来源是CCF,难度为入门。根据给出的代码,这是一个基于循环的算法,通过遍历[l,r]区间内的数,计算数对n取模后的最大值。具体的实现细节可以参考引用中的代码。这道题目属于入门级别,比较简单。 第二题是关于小熊的果篮。给定一个长度为n的数组a,其中连续的相同元素被视为一个块,要求按照块的顺序输出每个块的头元素,并删除已输出的元素。具体的实现细节可以参考引用中的代码。这道题目需要使用双链表来处理,时间复杂度为O(n)。 综上所述,CSP-J2021复赛的题目包括分糖果和小熊的果篮,具体的解题思路和代码实现可以参考上述引用内容。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [[CSP-J 2021]比赛题解](https://blog.csdn.net/weixin_56550385/article/details/126811201)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [新鲜出炉的 CSP-J 2021 复赛题目 题解](https://blog.csdn.net/qq_23109971/article/details/121024436)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值