HDU 6230 Palindrome ( Manacher && 树状数组)
题意 : 给定一个字符串S,问你有多少长度为 n 的子串满足 S[i]=S[2n−i]=S[2n+i−2] (1≤i≤n)
参考自 ==> 博客
分析 : 可以看出满足题目要求的特殊回文子串其实是根据 n 以及 2*n-1 对称的,如图所示
如果我设第一个对称点为 i 第二个为 j ,p[i] 为以 i 为中心是回文半径, 那满足题目条件的子串必定满足 j − p[j] + 1 ≤ i < j ≤ i + 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)
可能说的不太清楚,具体看看代码吧,看完了应该就懂了!
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; }
瞎 : 马拉车的 p[ ] 数组还有几个特性!
p[i]-1 为以 i 为中心的回文长度
p[i]/2 表示回文半径
i%2==0 表示这个位置为字符,i/2-1 表示原字符串的位置
i%2==1 表示为字符中间,这两边的字符在原字符串的位置分别为 i/2-1 和 i/2