CF Round #598(Div.3) F【含详细的思维过程】

题目大意

给定两个字符串s和t,长度为n,你可以对一个字符串其中长度为l的一段进行翻转,l取值为1…n,但同时也必须对另一个串选择长度为l的一段进行翻转,这两个翻转的起点终点可以不一样。问你是否有可能在经过若干次操作后使得两个串变得相同。可能输出YES,否则输出NO。

结论

先上结论。
如果两个串对应字符数量不一致那么肯定不行,输出NO;
如果数量一致并且s串中有相同字母,那么一定可以,输出YES。(t串也一样,俩串字母一样的)
如果木有相同字母,但是s串和t串的逆序对数的和为偶数,那么一定可以,输出YES。
如果连和都不是偶数就不行,输出NO。

我的思维

第一步,试着简化,看是否能只让一个串变化
我们记一个串是 s 1 s_{1} ,另一个为 s 2 s_{2} ,假设两个串经过x次操作可以变成相同的串 s s ,那么可以看成是 s 1 s_{1} 先经过了 x x 次翻转变成s,再经过 x x 次操作变成 s 2 s_{2} 。这个等价过程中要保持操作长度的一致性:假设x=3次操作长度依次为5,3,6,那么剩下3次操作长度依次必须是6,3,5,总操作长度组成的序列就是5,3,6,6,3,5。
第二步,特殊化,既然翻转长度l是可以任意选的,那么我们可以发现一切的翻转操作都可以等价于若干个长度为2的翻转操作,所以我们可以限定操作长度只能是2
第三步,长度为2的规定套入第一步,就会发现我们只要能经过偶数次长度为2的操作能将 s 2 s_{2} 变为 s 1 s_{1} ,那么就输出YES

不过想到这一步会发现还过不了一个样例。
s 1 s_{1} :ababa
s 2 s_{2} :baaba
我们对s2做了一次翻转操作两者就相同了,次数是奇数,于是输出NO,然而答案是YES。
这是因为 s 2 s_{2} 串中含有相邻的相同字母aa,于是我们就可以爱对它们两个做几次翻转就做几次翻转,就可以把操作次数化奇为偶

但是这个情况还不够一般,有可能原来的 s 1 s_{1} s 2 s_{2} 串都没有相邻的相同字母,但是经过翻转以后两个相同的字母可能会靠在一起。
我们考虑 s 1 s_{1} 如果里面含有两个相同的字母,但是不相邻。我们试着让后面那个向前移动(就是一直和前一个交换)。若第一个出现在pos1,第二个出现在pos2,那么第二个向前移动最少pos2-pos1-1次就可以使得两个相同字母相邻。
既然我们对 s 1 s_{1} 做了这么多次操作,我们也得对 s 2 s_{2} 做pos2-pos1-1次操作,为了方便描述将这个次数记为tmp。我们对 s 2 s_{2} 中随意选择两个相邻元素进行tmp次交换,会发现若tmp是偶数,相当于没交换,否则相当于交换。
如果tmp能是偶数就好了,所以灵机一动又可以想到s1中那两个字母既然已经相邻了,那我们可以利用他们两个化奇为偶。
于是我们得出结论:可以在不改变另一个串的情况下,将一个串的两个不相邻相同字母翻转至相邻。
所以只要有相同字母就可以相邻,然后我们就利用他们化奇为偶,从而保证了 s 2 s_{2} 串可以通过偶数次长度为2的翻转操作变为 s 1 s_{1} 串。

那么如果没有相同字母呢?
我们就以 s 1 s_{1} 为“模板”,每次在 s 2 s_{2} 里找到对应字母变换到对应位置。
【注意我们一直使用长度为2的翻转】
比如说 s 1 = l o v e u s_{1}=loveu s 2 = u l o v e s_{2}=ulove
第一步要做的就是 s 2 s_{2} 第一个位置搞成 l l
接下来第二、第三个位置依次是 o , v , e , u o,v,e,u
然后统计一下操作次数 c n t cnt 是不是偶数,是偶数输出YES,否则NO。

至此我们的问题就已经解决啦,但是我们还有更优雅的办法。
上面这种计算 c n t cnt 的方法要写三重循环(一重扫描 s 1 s_{1} ,第二重扫描 s 2 s_{2} ,第三重交换),不够快乐。
这里我们再利用一种思维——标准化思维
我们把 s 2 s_{2} 串先经过 c n t 1 cnt1 操作变为升序序列,再以这个升序序列为起点经过 c n t 2 cnt2 变化变为 s 1 s_{1} 串,两者的和就是总的操作次数。
你问这个有什么好处吗?
有哒,你应该已经可以看出来 c n t 1 cnt1 其实就是 s 1 s_{1} 串的逆序对数, c n t 2 cnt2 同理。那我们就只使用两重循环计算逆序对就可以了,写代码时免去考虑交换的细节,代码复杂度比较低。

最后再扯一下时间复杂度, O ( n 2 ) O(n^{2}) 计算逆序对是没问题的,不会超时。如果串长超26,必然有重复字母,不会到计算逆序对这一步。因此我们计算的串长最长也就26,总串长20W,就算有1W个都是长度为26的那 10000 O ( n 2 ) 10000*O(n^{2}) 完全可以接受。

上代码

代码比较丑,欢迎指正。

#include<bits/stdc++.h>
#define rep(i,l,r) for(int (i)=(l);(i)<=(r);(i)++)
#define per(i,l,r) for(int (i)=(r);(i)>=(l);(i)--)
using namespace std;
const int LEN=2e5+10;
int q,s1[LEN],s2[LEN],count1[27],count2[27];
int nixu(int s[],int len){
	int cnt=0;
	rep(i,2,len)
		rep(j,1,i-1)
			if(s[i]<s[j])
				cnt++;
	return cnt;
}
int main(){
	scanf("%d",&q);
	while(q--){
		int n;
		bool cnt_judge=true,repeat_judge=false;
		memset(count1,0,sizeof(count1));
		memset(count2,0,sizeof(count2));
		scanf("%d",&n);
		getchar();
		rep(i,1,n){
			s1[i]=getchar()-'a'+1;
			if((++count1[s1[i]])>1)
				repeat_judge=true;
		}
		getchar();
		rep(i,1,n){
			s2[i]=getchar()-'a'+1;
			count2[s2[i]]++;
		}
		rep(i,1,26)
			if(count1[i]!=count2[i])
				cnt_judge=false;
		if(cnt_judge==false)
			printf("NO\n");
		else if(repeat_judge)
			printf("YES\n");
		else if(((nixu(s1,n)+nixu(s2,n))&1)==0)
			printf("YES\n");
		else
			printf("NO\n");
	}
	return 0;
}
发布了49 篇原创文章 · 获赞 10 · 访问量 9284

猜你喜欢

转载自blog.csdn.net/TengWan_Alunl/article/details/103006819