天天看点

BZOJ 2342 [Shoi2011] 双倍回文 Manacher + set维护

题目大意:

就是现在给出一个长度为n的字符串(1 <= 2*10^5), 求最长双倍回文字串T的长度 (T是最长双倍回文字串当且仅当 T的长度是4的倍数且T是回文串, 其左半部分和右半部分都是回文串)

大致思路:

首先用manacher处理处所有字符为中心的回文半径, 然后用set维护对于每个正中心的位置查询左中心的位置即可 (论文题)

细节写在代码注释里了

代码如下:

Result  :  Accepted     Memory  :  18264 KB     Time  :  748 ms

/**************************************************************
    Problem: 2342
    User: Gatevin
    Language: C++
    Result: Accepted
    Time:748 ms
    Memory:18264 kb
****************************************************************/
 
/*
 * Author: Gatevin
 * Created Time:  2015/3/24 10:09:54
 * File Name: Chitoge_Kirisaki.cpp
 */
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;

/*
 * 首先可以Manacher处理出所有中心的回文半径
 * 然后枚举双倍回文的正中心的位置, 当然这个位置必须是'#'
 * 然后对于这样一个回文串, 要使得其实双倍回文, 在左边必须存在左中心j
 * 显然要满足的条件有j位置也是'#', 然后 (i - j)*2 - 1 + 1 <= R[i]
 * 所以左中心一定在[i - R[i]/2, i)之间为'#'的位置
 * 那么要做的就是在这个区间当中找到最小的回文半径能覆盖到正中心i的那个
 * 可以使用一个set来维护可能的'#'位置, 如果当以i为正中心, j为左中心不能覆盖到i
 * 那么j也就不能覆盖到比i更右边的正中心了, 可以从set中删去j, 节省查询时间
 * 这样集合当中的元素最多被删除n次, 时间复杂度O(nlogn)
 * 觉得有点不好说但是发现 n <= 2*10^5还是过了..
 */

#define maxn 1000100
 
int n;
char in[maxn >> 1], s[maxn];
int R[maxn];
set<int> S;
 
void Manacher(char *s, int *R, int n)
{
    int p = 0, mx = 0;
    R[0] = 1;
    for(int i = 1; i <= n; i++)
    {
        if(mx > i)
            R[i] = min(R[2*p - i], mx - i);
        else R[i] = 1;
        while(s[i - R[i]] == s[i + R[i]])
            R[i]++;
        if(i + R[i] > mx)
            mx = i + R[i], p = i;
    }
    return;
}
 
int main()
{
    scanf("%d", &n);
    scanf("%s", in);
    s[0] = '@';
    for(int i = 0; i < n; i++)
        s[2*i + 1] = in[i], s[2*i + 2] = '#';
    s[2*n] = '$';
    Manacher(s, R, 2*n);
    for(int i = 2; i <= 2*n - 4; i+= 2)
        S.insert(i);
    int ans = 0;
    for(int i = 4; i <= 2*n - 4; i += 2)//枚举可能是中心的'#'
    {
        int l = i - (R[i] >> 1);
        if(l & 1) l++;
        while(l != i)
        {
            if(l + R[l] >= i)
            {
                ans = max(ans, (i - l) << 1);
                break;
            }
            else
                S.erase(l), l = *S.upper_bound(l);
        }
    }
    printf("%d\n", ans);
    return 0;
}
           

继续阅读