[SHOI2011]双倍回文 manacher
题面:
题解:
首先有一个性质,本质不同的回文串最多O(n)个。
所以我们可以对于每个i,求出以这个i为结尾的最长回文串,然后以此作为长串,并判断把这个长串从中间劈开后左边的一半是否也是一个回文串(判断左边那半的中点的回文半径是否可以跨过当前长串的中点)。
复杂度O(n)
1 // luogu-judger-enable-o2 2 #include<bits/stdc++.h> 3 using namespace std; 4 #define R register int 5 #define AC 1001000 6 7 int n, pos, maxn, ans; 8 int r[AC]; 9 char a[AC], s[AC]; 10 11 void pre(){ 12 scanf("%d%s", &n, a + 1); 13 s[0] = '$', s[1] = '#'; 14 for(R i = 1; i <= n; i ++) s[2 * i] = a[i], s[2 * i + 1] = '#'; 15 } 16 17 inline void upmax(int &a, int b){ 18 if(b > a) a = b; 19 } 20 21 void manacher() 22 { 23 int b = 2 * n; 24 for(R i = 1; i <= b; i ++) 25 { 26 r[i] = (maxn > i) ? min(r[2 * pos - i], maxn - i + 1) : 1;//这里要取min 27 while(s[i - r[i]] == s[i + r[i]]) ++ r[i]; 28 29 int last = i - r[i] + 1, Next = i + r[i] - 1; 30 if(s[last] == '#') ++ last, -- Next; 31 if(i + r[i] - 1 > maxn && last <= i)//last才是整个串的开头 32 { 33 for(R j = maxn + 1; j <= Next; j ++) 34 { 35 int l = 2 * i - j;//获取串头 36 int sum = (j - l + 1 + 1) >> 1; 37 if(s[j] == '#' || sum % 4) continue;//中间是获取实际字符数 38 int tmp = (l + j) >> 1, k = (tmp + l) >> 1; 39 if(k + r[k] - 1 >= tmp) upmax(ans, sum); 40 } 41 } 42 43 if(i + r[i] - 1 > maxn) pos = i, maxn = i + r[i] - 1; 44 } 45 printf("%d\n", ans); 46 } 47 48 int main() 49 { 50 //freopen("in.in", "r", stdin); 51 pre(); 52 manacher(); 53 //fclose(stdin); 54 return 0; 55 }