知识点 manacher + 主席树/二维数点
给定长度为 n n n 的字符串,询问其中满足以下要求的回文串的个数
用 manacher 算法预处理后对于任意以 #
为中心的回文串都是原串的一个偶回文, 所以对于当 s i = s_i = si= #
所有以 i i i 为中心的回文子串都满足条件1, 考虑满足条件2的有多少个。
设以 i i i 为中心的最大回文串的左右端点为 [ l = i − p i + 1 , r = i + p i − 1 ] [l = i - p_i + 1, r = i + p_i - 1] [l=i−pi+1,r=i+pi−1] 设存在以 j j j 为中心,回文半径为 k k k 的回文串满足 l < j < i l < j < i l<j<i 且 j + k − 1 = i j + k - 1 = i j+k−1=i, j − k + 1 > = l j - k + 1 >= l j−k+1>=l。
这样的回文串就是题目所询问的左边那一半的回文,因为回文对称,所以我们只需要询问左边这样的个数,右边自然会有对应符合要求的。
但由于 manacher 算法 p i p_i pi 维护的以 i i i 为中心的回文串的最大回文半径这样的回文串可能是 j + p j − 1 > i j + p_j - 1 > i j+pj−1>i 但他们的子串是符合的,所以只要统计 j + p j − 1 > = i j + p_j - 1 >= i j+pj−1>=i 的即可。注意这样的回文串的左边界不能超过 l l l, (i - j <= j - l)。
用主席树维护权值树下标为 i + p i − 1 i + p_i - 1 i+pi−1,每次查询 [ j , i − 1 ] [j, i - 1] [j,i−1] 区间中值大于等于当前 i i i 的即可,因为算上 #
manacher 维护的都是奇数长度的回文串 所以 j = ( i + ( i − p [ i ] + 1 ) ) / 2 j = (i + (i - p[i] + 1)) / 2 j=(i+(i−p[i]+1))/2。
#include
using namespace std;
#define ll long long
const int N = 2e5 + 10;
string a;
char s[N];
int n, p[N];
void manacher(){
int k = 0;
s[++ k] = '$'; s[++ k] = '#'; // 开始符号
for(int i = 1; i <= n; i ++){
s[++ k] = a[i - 1]; s[++ k] = '#';
}
s[++ k] = '^'; // 截止符防止越界
n = k;
int mid = 0, max_r = 0; // mid:当前出现过的回文串中右边界最大的回文中心 max_r:出现过的回文最大右边界(开区间)
for(int i = 1; i <= n; i ++){
if(i < max_r) p[i] = min(p[mid * 2 - i], max_r - i);
else p[i] = 1;
while(s[i - p[i]] == s[i + p[i]]) p[i] ++;
if(i + p[i] > max_r){
mid = i;
max_r = i + p[i];
}
}
}
struct node{ // 维护的是多版本的权值树
int ls, rs, sum;
}tr[N * 40];
int T[N], tot;
void build(int &now, int pre, int loc, int l, int r){
now = ++ tot;
tr[now] = tr[pre];
tr[now].sum ++;
if(l == r) return ;
int mid = (l + r) >> 1;
if(loc <= mid) build(tr[now].ls, tr[pre].ls, loc, l, mid);
else build(tr[now].rs, tr[pre].rs, loc, mid + 1, r);
}
int query(int lth, int rth, int ql, int qr, int l, int r){ // 询问版本 lth ~ rth 之间 区间 [ql, qr] 的和
if(l >= ql && r <= qr) return tr[rth].sum - tr[lth].sum;
int mid = (l + r) >> 1, ans = 0;
if(ql <= mid) ans += query(tr[lth].ls, tr[rth].ls, ql, qr, l, mid);
if(qr > mid) ans += query(tr[lth].rs, tr[rth].rs, ql, qr, mid + 1, r);
return ans;
}
void init(){
for(int i = 1; i <= tot; i ++) tr[i] = {0, 0, 0};
for(int i = 1; i <= n; i ++) T[i] = 0;
tot = 0;
}
void solve(){
cin >> a;
n = a.length();
manacher();
init();
ll ans = 0;
for(int i = 1; i <= n; i ++){ // 考虑枚举回文中心
/*
si = # pi 半径以内以i为中心的子串都满足条件1 考虑计算满足条件2 的个数 区间为 [l = i - p[i] + 1, r = i + p[i] - 1]
在该区间内只要满足 j + p[j] - 1 >= i 的点j都符合要求 (i - j <= j - l)
*/
if(s[i] == '#') { // 只有以 # 为中心的才是偶长度的回文串
int j = (2 * i - p[i] + 1) / 2;
ans += query(T[j - 1], T[i - 1], i, n, 1, n);
}
build(T[i], T[i - 1], i + p[i] - 1, 1, n);
}
cout << ans << "\n";
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t;
cin >> t;
while(t --){
solve();
}
return 0;
}