【洛谷 P4555】 [国家集训队]最长双回文串 (Manacher)

题目链接
\(|S|<=10^5\),时间还是很宽松的。
允许我们使用线性/\(N\log N\)/甚至\(N \sqrt N\)的算法。
\(l[i]\)表示以\(a[i]\)结尾的最长回文串,\(r[i]\)表示以\(a[i]\)开头的最长的回文串,
那么答案很显然就是\(\max_{i=1}^{len-1}l[i]+r[i+1]\)
怎么求?
回顾一下我们的马拉车算法

for(int i = 1; i < len; ++i){
   if(i < maxright)
     hw[i] = min(hw[(mid << 1) - i], hw[mid] + mid - i);    //min左边的参数是这个点的对称点的hw值,右边的是保证这个部分在这个大回文串之内
   else hw[i] = 1;
   while(a[i + hw[i]] == a[i - hw[i]]) ++hw[i];  //拓展
   if(hw[i] + i > maxright){   //更新右端点
     maxright = hw[i] + i;
     mid = i;
   }
}

我们在每个\(i\)处理出\(hw[i]\)后更新\(i\)~\(i+hw[i]-1\)\(l\)值,每个位置只需要更新一次就好了,因为我们是从左到右遍历的,因此第一次更新的一定是最优值。所以我们只需要定义一个变量\(p\),表示已经更新到哪里了,然后每次\(for(p->i+hw[i]-1)\),更新\(l\)值,如果\(p\)已经超过\(i+hw[i]-1\),是不会更新的,保证每个位置只被更新一次,也就是保证了时间复杂度是线性的。\(r\)也同理,反过来跑一遍就好了。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 100010;
char b[MAXN], a[MAXN << 1];
int hw[MAXN << 1], l[MAXN], r[MAXN], ans, n, p = 1;
int main(){
    scanf("%s", b);
    a[0] = a[1] = '#';
    int len = strlen(b);
    for(int i = 0; i < len; ++i)
       a[(i << 1) + 2] = b[i], a[(i << 1) + 3] = '#';
    int maxright = 0, mid; len = (len << 1) + 3;
    for(int i = 1; i < len; ++i){
       if(i < maxright)
         hw[i] = min(hw[(mid << 1) - i], hw[mid] + mid - i);
       else hw[i] = 1;
       while(a[i + hw[i]] == a[i - hw[i]]) ++hw[i];
       if(hw[i] + i > maxright){
         maxright = hw[i] + i;
         mid = i;
       }
       for(; p < i + hw[i]; ++p) l[p] = (p - i) + 1 - (a[p] == '#');
    }p = len - 1;
    for(int i = len - 1; i; --i)
       for(; p > i - hw[i]; --p)
          r[p] = (i - p) + 1 - (a[p] == '#');
    for(int i = 1; i < len - 1; ++i)
       ans = max(ans, l[i] + r[i + 1]); 
    printf("%d\n", ans);
    return 0;
}

posted @ 2018-10-04 20:31  Qihoo360  阅读(190)  评论(0编辑  收藏  举报
You're powerful!