题意,给定一个100000 的串,求他一个子串,使得将子串分成三部分有后,第一部分=第三部分,第一部分与第二部分对称(回文)
首先我们需要处理出以i为轴的回文串的两端,这个事情可以用Manacher算法完成,复杂度O(n)
http://blog.csdn.net/ggggiqnypgjg/article/details/6645824/
这个博客写的很好懂。不会的童鞋可以去学习一下这个算法,非常精妙。
好的现在我们已经会了这个算法,并获得了每个点为轴的串的右端点p[i]
很简单地可以处理出左端点。干脆把左右端点定义为left[i] 和right[i]
观察一下,以一部分和第二部分对称,第一部分=第三部分,会发现第二部分和第三部分也是对称的。
且由于两个部分对称,故回文串一定是偶数长度,也就是说是通过‘#’枚举出来的。(因为是数字代码里用-1代表#)
故对于现在枚举到的点,设为x,我们只枚举#点。
看图
听说语文不好的人都喜欢看图说话 = =
对于枚举的x。我们要找到一个y,使得y<=right[x],且x >= left[y]
看上面比较好理解,也可以自己画一画如果不是这样会怎么样。
这样的话,紫色的串和两边的绿色串分别对称。其中和左边的绿色关于x对称,右边的关于y对称。原因是没有超出他们最长对称区间。
因为每个区间是等长的,我们考虑最好计算的紫色区间,他的长度是y-x之类的,或者再加一,不要在意这些细节,这些后面处理。
要找最长的,我们需要使得y最大,这样区间最长。
于是问题就变成了,枚举x时(其实也就是枚举第二部分的起点时),找到一个最大的y并满足上面那个条件。
现在问题就演变成了如何找这个y,
无聊搜居然搜到了有的博主直接从right[i]开始往左找,这居然能过!(不过大概数据不好构造?),我真是= =。。。。。。。。。。。。。。。。。。。。。。。。。
撑死胆大的。
可以用set维护这个,但是作为非stl选手,我写了个线段树(妈妈我第一次在赛时写线段树T T 紧张,怀念前队友,好像跟之前的队友的时候我除了图论啥都不用写23333)
set我再学学吧,先来个线段树版本。
在线段树上,区间维护的是轴在这个区间内的回文串,最左端可以到的位置。
也就是对于(l, r, rt) 维护minleft[rt] = min(left[y] | l<=y<=r)
应该是用线段树辅助二分的思想?或者说用排序树的思想?总觉得他们是共通的。。> <
上面说了我们只用计算#这样的点的情况就可以了,所以可以重新对#编号,并寻找他们和原编号的对应关系(随便写几个就知道了)。然后现在left 和right的含义稍稍更新,不过也差不多。
现在我们从右向左枚举,对于枚举的x点,我们在1-left[x]区间内,优先向右儿子找,找到即返回。
找一个子树的要求是,minleft[rtofson]<= x,否则连这个区间里最小值都不小于x,就不可能找到left[y]<=x的y了。
每次写题解都这么啰嗦难怪我不喜欢写题解> <
其他看代码吧。
1 #include <cstring> 2 #include <cstdio> 3 #include <algorithm> 4 using namespace std; 5 #define lson l,mid,rt<<1 6 #define rson mid+1,r,rt<<1|1 7 const int N = 200010; 8 struct point{ 9 int left, y, right; 10 point(){}; 11 point(int a, int b, int c){left = a, y = b, right = c;}; 12 }info[N];// 之对于#号 13 int minleft[N<<1]; 14 int p[N], left[N]; 15 int ch[N], str[N]; 16 17 18 void pushup(int x){ 19 minleft[x] = min(minleft[x<<1] , minleft[x<<1|1]); 20 } 21 22 int query(int L, int R, int l, int r, int rt){ 23 if(l == r){ 24 if(info[l].left <= L) return l; 25 return -1; 26 } 27 int mid = (l + r) >> 1, ans = -1; 28 //仅当minleft[rtson]的时候找那个儿子 29 //保证y最大,故优先往右,找到即返回 30 if(mid + 1 <= R && minleft[rt<<1|1] <= L) ans =query(L, R, rson); 31 if(ans != -1)return ans; 32 if(minleft[rt<<1] <= L) ans = query(L, R, lson); 33 return ans; 34 } 35 void modify(int pos, int l, int r, int rt){ 36 if(l == r && l == pos){ 37 minleft[rt] = info[l].left; 38 return; 39 } 40 int mid = (l + r )>> 1; 41 if(pos <= mid)modify(pos, lson); 42 else modify(pos, rson); 43 pushup(rt); 44 } 45 int main(){ 46 int n, id, mxpos, i, j, ans, y; 47 int TC,tc; 48 scanf("%d", &TC); 49 for(tc = 1; tc <= TC; tc++){ 50 scanf("%d", &n); 51 for(i = 1; i <= n; i++) scanf("%d", &ch[i]); 52 //manacher 处理 53 str[0] = -2; 54 id = 0; 55 for(i = 1; i <= n; i++){ 56 str[i<<1] = ch[i]; 57 str[i*2-1] = -1; 58 } 59 mxpos = 0; 60 ans = 0; 61 str[n<<1|1] =-1; 62 n = n << 1 | 1; 63 for(i = 1; i < n; i++){ 64 if(mxpos > i){ 65 p[i] = min(p[2*id-i], mxpos - i); 66 }else{ 67 p[i] = 1; 68 } 69 for(; str[i+p[i]]==str[i-p[i]]; p[i]++){ 70 ; 71 } 72 ans = max(ans, p[i] - 1); 73 if(p[i] + i > mxpos){ 74 mxpos = p[i] + i; 75 id = i; 76 } 77 left[i] = i - (p[i]-1); 78 } 79 id = 0; 80 for(i = 1; i < n; i++){ 81 if(i & 1){ 82 //对所有的#重新编号,并处理左右端的#的标号 83 info[++id] = point((left[i]+1)/2, i, (i + p[i]) /2); 84 } 85 } 86 ans = 0; 87 memset(minleft, 0x3f, sizeof(minleft)); 88 for(i = id; i >= 1; i--){ 89 y = query( i, info[i].right , 1, id, 1); //query() 90 if(y != -1) 91 ans = max(ans, (y-i)*3); 92 modify(i, 1, id, 1); 93 } 94 printf("Case #%d: %d\n", tc, ans); 95 } 96 return 0; 97 }
PS:之前写Python作业,其实感觉先写出每个模块的作用对理清思路很有用。。。不过比赛的时候时间短+不太复杂的问题总是懒得写。。
HDU 5371 Hotaru's problem manacher+(线段树or set)