HDU 6230 Palindrome ( Manacher && 树状数组)

题意 : 给定一个字符串S,问你有多少长度为 n 的子串满足  S[i]=S[2ni]=S[2n+i2(1in)

 

参考自 ==> 博客

分析 : 可以看出满足题目要求的特殊回文子串其实是根据 n 以及 2*n-1 对称的,如图所示

如果我设第一个对称点为 i 第二个为 j ,p[i] 为以 i 为中心是回文半径, 那满足题目条件的子串必定满足  − p[j≤ ≤ p[i− 1 

简单点说就是两点的回文半径要相互覆盖

p[]数组很容易使用 Manacher 算法算出来,那么该如何去找答案?(当时就是想到马拉车,然后完全不知道怎么快速找答案,我好菜啊!)

我们从小到大枚举 j , 对于它左边的所有的点找出满足条件的 i ,即 i + p[i] >= j ,那么我哪知道 j 左边的哪些点是能够成为满足条件的 i ?

由于是从小到大更新,我们将枚举过的 j 装进一个根据 j + p[j] 长度升序的优先队列中,然后使用树状数组在 j 这个点更新贡献 + 1

在枚举下一个 j 的时候,我们将优先队列里面的元素一个个取出来(优先队列里面的元素其实就是现在 j 左边的点),取出来有我们需要去掉不满足条件的点 即 覆盖不到当前 j 的点,直到队头点满足条件或者队列为空,然后对于枚举的每一个 j 贡献出来的答案就是树状数组的前缀和 sun(i) - sum(i-p[i]-1)

可能说的不太清楚,具体看看代码吧,看完了应该就懂了!

 

#include<bits/stdc++.h>
#define lowbit(i) (i&(-i))
using namespace std;
const int maxn = 500000 + 5;
char s[maxn], sNew[maxn<<1];
int p[maxn<<1], id, mx=0;
int c[maxn];

int Init()
{
    int len = strlen(s);
    sNew[0] = '$';
    sNew[1] = '#';
    int j = 2;
    for (int i = 0; i < len; i++){
        sNew[j++] = s[i];
        sNew[j++] = '#';
    }
    sNew[j] = '\0';
    return j;
}

int Manacher()
{
    int len = Init();
    int max_len = -1;
    mx = 0;
    for (int i = 1; i < len; i++){
        if (i < mx) p[i] = min(p[2 * id - i], mx - i);
        else p[i] = 1;

        while (sNew[i - p[i]] == sNew[i + p[i]]) p[i]++;

        if (mx < i + p[i]){
            id = i;
            mx = i + p[i];
        }
    }
    return len;
}

inline void add(int i, int val)
{
    while(i <= maxn-5){
        c[i] += val;
        i += lowbit(i);
    }
}

int sum(int i)
{
    int ret = 0;
    while(i > 0){
        ret += c[i];
        i -= lowbit(i);
    }
    return ret;
}

struct cmp{
    bool operator () (int &A, int &B) const{
        return A + p[A] > B + p[B];
    };
};
priority_queue<int, vector<int>, cmp> que;

int main(void)
{
    int nCase;
    scanf("%d\n", &nCase);
    while(nCase--){
        scanf("%s", s);
        int len =  Manacher();

        for(int i=1, j=2; j<len; i++,j+=2)
            p[i] = p[j]/2 - 1;

        while(!que.empty()) que.pop();
        memset(c, 0, sizeof(c));
        long long ans = 0;
        for(int i=2; i<=(len-1)/2; i++){
           // printf("%d ", p[i]);
            while(!que.empty()){
                int now = que.top();
                if(now + p[now] < i){///去掉不合格的点!
                    add(now, -1);
                    que.pop();
                }else break;
            }
            ans += sum(i) - sum(i-p[i]-1);
            que.push(i);
            add(i, 1);
        }//puts("");
        printf("%lld\n", ans);
    }
    return 0;
}
View Code

 

瞎 : 马拉车的 p[ ] 数组还有几个特性!

p[i]-1 为以 i 为中心的回文长度

p[i]/2 表示回文半径

i%2==0 表示这个位置为字符,i/2-1 表示原字符串的位置

i%2==1 表示为字符中间,这两边的字符在原字符串的位置分别为 i/2-1 和 i/2

posted @ 2017-12-06 17:04  qwerity  阅读(320)  评论(0编辑  收藏  举报