[我们这里还有鱼] Problem B. 序列
对于一个长度为偶数的序列 a1,a2,a3,…,an, 定义这个序列为好的序列,当且仅当
a1 + an = a2 + an−1 = a3 + an-2 = ……
定义一个对序列的翻滚操作,使所有元素向前移一个位置,第一个元素移到最后的位置。 现在小 A 有一个长度为偶数的序列 b1,b2,b3,…,b3,他想知道至少需要翻滚多少次才能使这个序列 成为好的序列。
马拉车是什么?
(大误)
Manacher’s Algorithm,是一个叫做Manacher的人发明的一种线性求字符串最长回文子串的方法。
Manacher’s Algorithm
现在我们有两种回文串的类型:CABAC & CABBAC,那么怎么处理呢?如果分类讨论的话太丑陋,一个好的算法就应该男女通吃。我们考虑在每两个字符中插入一个’#’,这样的话就成了#C#A#B#A#C# & #C#A#B#B#A#C#,我们可以发现,这两个字串都是一种类型的了:从某一个字符向两边对称。再考虑这个字串的长度,因为在每个字符的右边加了一个’#’,又在字符串末尾加了一个’#’,所以有 S处理后字符串=S原字符串*2+1。
我们在暴力找回文子串的时候,是枚举当每个字符为一个回文子串的中心,再向两边暴力拓展。在这里我们可以把每一个以第i个字符为中心的最长回文子串的半径记为P[i] (我们考虑半径就是$(Len+1)/2$ ,反推则回文子串长度为$2*P[i]-1$)
接下来我们考虑一个问题,ABCBABCBA这个字符串,我把它的P数组记录下来:
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
P[i] | 1 | 1 | 3 | 1 | 5 | 1 | 3 | 1 | 1 |
我们先考虑P[2],P[2]的最长回文子串为:ABCBA。我们再考虑P[6],P[6]的最长回文子串也为:ABCBA。排除巧合的可能,真相只有一个,这一定有什么原因在里面。我们考虑第4位,不难发现,由第4位拓展出的回文子串覆盖了0~8位字符。那么,0~3位和5~8位应该是完全对称的!所以我们算到在第4位后面的字符的时候,它的初始P[i]就应该是对称回去的P值!
接下来我们考虑怎么实现这个对称优化:我们维护两个值,mx,id。mx表示算到现在之前的回文子串所覆盖到的最右点,id表示覆盖到该最右点的子串的中心点。在计算某一个字位的P值时,我们先看一下该字位是否在之前的回文子串所覆盖的范围内,如果在,则该P值先赋为对称过去的那个点的P值。但是,我们还得注意,对称过去的P值有可能会超出了id所在回文子串的覆盖范围。碰到这种情况,只需要在P[id*2-i]和mx-i取min值就行了。另外当当前字位不在mx范围内,不妨先把半径设为1,表示目前可知最大回文就是它本身。赋完初值后,接下来再暴力向外拓展就行了。
1 | if (mx>i) p[i]=min(p[id*2-i],mx-i); |
现在我们知道了字符串每一位的P值,我要是想知道以某一位(一般来讲是求最大位)为中心的回文子串在原串中的起始位置呢?
我们考虑一下,字符串是”%QXQ”这种情况。现在我们知道的P值,是以”#%#Q#X#Q#”为基础的。考虑’X’位,它的半径是4,在处理后的字串中位置是5。当我们用该位置减去(半径/2)(半径中原字符和’#’数量相等),就得到了在回文子串前一位的字符’%’的位置。我们可以发现,前面的原字符和’#’数量也等,于是也要除以2。于是我们得到了这样的公式(设处理后字符的位置为W0):W原字符=(W0+1-P[W0])/2 (要注意字符串开头标号是0哦)
这样我们得到了以下代码:
1 | #include<bits/stdc++.h>//祈福无Bug |
我们考虑他的复杂度,这个算法求解就是一个对mx更新的过程,对mx的更新是线性的,所以考虑复杂度为$O(n)$。
对马拉车算法的应用(在模拟赛中是马翻车)
Problem B. 序列
输入文件: sequence.in
输出文件: sequence.out
时间限制: 1 秒
内存限制: 512 MiB
题目描述:
对于一个长度为偶数的序列 a1, a2, a3, . . . , an,定义这个序列为好的序列,当且仅当 a1 + an = a2 + an−1 = a3 + an−2 = . . . = a n2+ a n2 +1。定义一个对序列的翻滚操作,使所有元素向前移一个位置,第一个元素移到最后的位置。现在小 A 有一个长度为偶数的序列 b1, b2, b3, . . . , bn,他想知道至少需要翻滚多少次才能使这个序列成为好的序列。
输入:
第一行一个整数 n 表示序列的长度,保证 n 为一个偶数。第二行 n 个正整数,第 i 个数表示 bi。
输出:
如果有解,输出一个正整数,表示最少需要翻滚多少次才能使这个序列成为好的序列。如果没有解,输出 IMPOSSIBLE。
样例输入:
6
2 8 7 9 1 3
样例输出:
1
限制:
对于 30% 的数据,n ≤ 1000。
对于 60% 的数据,n ≤ 200000。
对于 100% 的数据,n ≤ 1000000,bi ≤ 1000000。
咋一看这道题,非常棘手,但是在经过我两个小时的仔细思考后,我还是觉得非常棘手。
我们可以观察一下样例,对成功变化过的样例相邻两数做差。
我们发现了什么!回……回文子串!
对于任意这个数列中相对的任意两组数,我们把他设为 a b B A,a+A=b+B,进行简单的移位后,我们得到a-b=B-A,两两相邻的数相减,就可以得到一个回!文!数!列!所以求差后的如果能得到回文序列的序列,就是”好的序列”!
接下来我们考虑进行马拉车了。考虑一个有解的数列,它一定是由两个”好的序列”构成的。假设一个”好的序列”,将它的最后一位移到第一位,第一位与原第二位相加还是符合原数列的标准,所以第一位和第二位可以构成一个”好的序列”。再把后面的数移上来……以此类推。
那么接下来我们的任务就是找到两个刚好填满空间的回文序列,并且使前一个序列尽可能小。 我们考虑回文序列为数组b,原序列为数组a。
1 | b[i]=a[i-1]-a[i]; |
于是从前往后枚举,判断每一位作为前一个回文序列的中心,写下以下代码:
1 | if (p[i]==i && p[n/2+i]==n/2-i+1){ |
其中计算i对应的另外一个序列的中心的位置很容易算错……
完整代码以下:
1 | #include<bits/stdc++.h>//祈福无Bug |
咕咕咕!QXQ快找彩蛋!
コメント