【题解】 区间本质不同回文串个数 PAM+LCT+线段树
Legend
见标题。
Editorial
考虑区间本质不同子串个数的做法。
唯一的区别是,此时回文串的长度不连续,没法直接线段树了。直接暴力的话,复杂度会直接退化到 \(O(n^2 \log n)\)。
不过我们有一个性质:
lamma:所有回文后缀按照长度排序后,可以划分成 \(O(\log n)\) 段等差数列。
证明见 https://oi-wiki.org/string/pam/
这个性质有什么用呢?等差数列也不能直接线段树呀。。。。
不过还有另一个神奇性质。
我们发现,每次新增的回文子串只导致从它到根每个等差数列对应的回文子串都是向右移动了一(这里的“一”指的是公差)位。
中间的位置虽然移动了,但是被它后面的一个位置覆盖了。因此只需要把头尾部在线段树里面做一下单点修改就好了。
特别地,当前这个结点所在等差数列是新增了一个“最长的回文串”,而不是平移一位。
所以要进行尾部 +1,和上一次出现这个“最长的回文串”的位置 -1。
不过在具体实现上并不需要分这两种情况讨论。
Code
我写的时候把线段树换成了树状数组以减小常数。
长篇预警。
#include <bits/stdc++.h>
#define debug(...) ;//fprintf(stderr ,__VA_ARGS__)
#define __FILE(x)\
freopen(#x".in" ,"r" ,stdin);\
freopen(#x".out" ,"w" ,stdout)
#define LL long long
const int MX = 2e5 + 23;
const LL MOD = 998244353;
int read(){
char k = getchar(); int x = 0;
while(k < '0' || k > '9') k = getchar();
while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
return x;
}
int n ,q;
struct BIT{
LL data[MX];
void add(int x ,int v){while(x < MX && x > 0) data[x] += v ,x += x & -x;}
LL sum(int x){LL s = 0; while(x > 0) s += data[x] ,x -= x & -x; return s;}
LL sum(int l ,int r){return sum(r) - sum(l - 1);}
}T;
int cnt;
struct LCT{
#define lch(x) ch[x][0]
#define rch(x) ch[x][1]
LCT(){
memset(ch ,-1 ,sizeof ch);
memset(fa ,-1 ,sizeof fa);
}
int ch[MX][2] ,fa[MX] ,t[MX] ,cov[MX];
int get(int x){return x == rch(fa[x]);}
int Nroot(int x){return fa[x] != -1 && (get(x) || x == lch(fa[x]));}
void pushup(int x){
}
void docov(int x ,int v){cov[x] = t[x] = v;}
void pushdown(int x){
if(cov[x]){
if(~lch(x)) docov(lch(x) ,cov[x]);
if(~rch(x)) docov(rch(x) ,cov[x]);
cov[x] = 0;
}
}
void rotate(int x){
int f = fa[x] ,gf = fa[f] ,which = get(x) ,W = ch[x][!which];
if(Nroot(f)) ch[gf][get(f)] = x;
ch[x][!which] = f ,ch[f][which] = W;
if(~W) fa[W] = f;
fa[f] = x ,fa[x] = gf ,pushup(f) ,pushup(x);
}
int stk[MX] ,dep;
void splay(int x){
++cnt;
if(cnt % 1000000 == 0) debug("%d\n" ,cnt);
int f = x; stk[++dep] = x;
while(Nroot(f)) stk[++dep] = f = fa[f];
while(dep) pushdown(stk[dep--]);
while(Nroot(x)){
if(Nroot(f = fa[x])) rotate(get(x) == get(f) ? f : x);
rotate(x);
}pushup(x);
}
void access(int x){
for(int y = -1 ; ~x ; x = fa[y = x]){
splay(x) ,rch(x) = y ,pushup(x);
}
}
int getT(int x){
splay(x); return t[x];
}
void link(int x ,int f){
fa[x] = f;
}
#undef lch
#undef rch
}lct;
int diff[MX] ,top[MX] ,ED[MX];
struct PAM{
PAM(){
top[1] = 1;
top[0] = 0;
memset(S ,0x3f ,sizeof S);
Slen = 0;
las = 0 ,tot = 1;
a[0].fail = 1;
a[0].len = 0;
a[1].len = -1;
}
int las ,tot;
int S[MX] ,Slen;
struct NODE{
int ch[26] ,len ,fail;
}a[MX];
int getfail(int x ,int p){
while(S[p] != S[p - a[x].len - 1]) x = a[x].fail;
return x;
}
void extend(int c){
S[++Slen] = c;
int x = getfail(las ,Slen);
if(!a[x].ch[c]){
++tot;
a[tot].len = a[x].len + 2;
a[tot].fail = a[getfail(a[x].fail ,Slen)].ch[c];
lct.link(tot ,a[tot].fail);
a[x].ch[c] = tot;
diff[tot] = a[tot].len - a[a[tot].fail].len;
if(diff[tot] == diff[a[tot].fail]) top[tot] = top[a[tot].fail];
else top[tot] = tot;
}
ED[Slen] = las = a[x].ch[c];
}
}A;
char str[MX];
struct QUERY{
int l ,r ,id;
bool operator <(const QUERY& B)const{
return r < B.r;
}
}Q[MX];
void walk(int x ,int Tim){
int cur = x;
lct.access(x);
for( ; x > 1 ; x = A.a[top[x]].fail){
T.add(lct.getT(x) - A.a[x].len + 1 ,-1);
T.add(Tim - A.a[top[x]].len + 1 ,1);
}
lct.splay(0);
lct.docov(0 ,Tim);
}
int output[MX];
int main(){
__FILE(c);
n = read() ,q = read();
scanf("%s" ,str + 1);
for(int i = 1 ; i <= n ; ++i){
A.extend(str[i] - 'a');
}
for(int i = 1 ,l ,r ; i <= q ; ++i){
l = read() ,r = read();
Q[i] = (QUERY){l ,r ,i};
}
std::sort(Q + 1 ,Q + 1 + q);
int R = 1;
for(int i = 1 ; i <= q ; ++i){
while(R <= Q[i].r){
walk(ED[R] ,R);
++R;
}
output[Q[i].id] = T.sum(Q[i].l ,Q[i].r);
}
for(int i = 1 ; i <= q ; ++i){
printf("%d\n" ,output[i]);
}
return 0;
}