Problem
给定一个长度为n的字符串S和m个询问,每次询问给出区间[l,r],求区间S[l..r]内回文子串的个数。
Hint
Solution
20points:Manacher or 回文自动机
第0~3个点的n*m较小,我们可以直接把询问区间提取出来,
做一遍Manacher或者回文自动机。
时间复杂度:
。
Code
我并没有打╮(╯▽╰)╭
100points:Manacher+主席树
先使用Manacher的惯用套路:在原串之间加入分隔符,得到一个长度为2n+1的新串。
例:aabb→&a&a&b&b&
这样就可以优雅地解决长度为偶数的回文串了。
易知以i为中心的回文串个数为
。
原询问[l,r]在新串中对应[2l-1,2r+1],令L=2l-1,R=2r+1,囿于回文串的左/右端点不能超过L/R,可得:
考虑将区间[L,R]折半处理。令mid=(L+R)/2=l+r,则当k≤mid时,min{g[k],k-L,R-k}=min{g[k],k-L}。
转化一下式子:
用一棵(权值线段树)主席树存储g[k]-k的cnt和sum,每次算完一个g[k]就插进主席树内。查询的时候,我们以-L为中点,左边(g[k]-k≤-L)的区间直接返回sum;右边(g[k]-k>-L)的区间返回cnt*(-L)。当然,这个g[k]-k可能是负数,-L肯定是负数;我们又知道负数区间的权值线段树可能会出问题;所以可以先给它加上个大数。
对于k>mid的做法类似。
时间复杂度: 。
Code
#include <cstdio>
#include <algorithm>
#include <cstring>
#define A(v) t[v].l
#define B(v) t[v].r
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int N=130050,M=N<<1;
char str[M],s[N];
int i,n,m,len,p[M],r1[M],r2[M],tot,px,l,r,L,R,mid;
ll ans;
struct node
{
ll s;
int v,l,r;
}t1[40*M],t2[40*M];
void modify(node*t,int&v,int x,int y)
{
t[++tot]=t[v]; t[v=tot].v++; t[v].s+=(ll)px;
if(x==y) return;
int mid=x+y>>1;
px<=mid?modify(t,A(v),x,mid):modify(t,B(v),mid+1,y);
}
ll query(node*t,int l,int r,int x,int y)
{
if(y<=px) return t[r].s-t[l].s;
if(x> px) return 1ll*(t[r].v-t[l].v)*px;
int mid=x+y>>1;
return query(t,A(l),A(r),x,mid) + query(t,B(l),B(r),mid+1,y);
}
//President_Tree
void Manacher()
{
int mx=0,idx=0;
fo(i,1,len-1)
{
p[i]=mx>i?min(p[2*idx-i],mx-i):1;
while(str[i+p[i]]==str[i-p[i]]) p[i]++;
if(i+p[i]>mx) mx=i+p[i],idx=i;
px=p[i]-1-i+M; modify(t1,r1[i]=r1[i-1],1,M<<1);
px=p[i]-1+i; modify(t2,r2[i]=r2[i-1],1,M<<1);
}
}
inline ll sum(ll a,ll b){return (a+b)*(b-a+1)/2;}
int main()
{
freopen("gene.in","r",stdin);
freopen("gene.out","w",stdout);
scanf("%d%d%s",&n,&m,s);
len=2*n+2;
str[0]='$';
fo(i,0,n)
{
str[2*i+1]='#';
str[2*i+2]=s[i];
}
Manacher();
fo(i,1,m)
{
scanf("%d%d",&l,&r);
L=2*l-1; R=2*r+1; mid=l+r;
ans=sum(L,mid)-sum(mid+1,R);
px=M-L; ans+=query(t1,r1[L-1],r1[mid],1,M<<1)-1ll*M*(mid-L+1);
px=R; ans+=query(t2,r2[mid],r2[R ],1,M<<1);
printf("%lld\n",(ll)r-l+1+ans>>1);
}
}