题目描述
白兔有一个长度为n的字符串。
白云有m个询问,每个询问会询问一段区间的本质不同回文子串个数。
(虽然原题没说,但是字符串只包含小写字母)
输入描述
第一行两个整数n, m。
接下来一行一个长度为n的字符串。
接下来m行,每行两个整数l, r。
输出描述
为了避免输出占用太多运行时间,你只需要输出 即可。
数据范围
解题思路
有一个结论:
一个字符串的所有回文后缀的长度,可以形成k个等差数列,k是log级的。
考虑前R个字符组成的字符串,对于一个等差数列,假设组成这个等差数列的回文串,最短的叫a,最长的叫b。对于右端点是R的询问,左端点在[b上一次出现的位置末尾+1,a的开头]这一段区间的话,ans要+1。
这么描述有点抽象,用图像表述就是:
什么?你要证明?去看吉老师论文去
这样一来就比较好办,首先建立一棵回文树,那么等差数列就是一些顺着fail来的东西,我们把每个等差数列的首项记录下来。
然后建立fail树,得到dfs序。我们知道,如果回文树上一个节点代表的回文串出现了,那么fail树上它的祖先们也出现了,于是按照dfs序弄一棵线段树,维护每一个回文树上节点代表的回文串最晚出现位置。
将原字符串从1到n依次处理,每次找到当前这个字符处在回文树上的节点,然后用跳fail的方式处理每一个等差数列,用树状数组来维护答案(在线段树上查询出b上一次出现的位置,然后根据以上结论得知答案被更新的区间,树状数组区间加即可)。
这么说可能还是很抽象,不过自认为代码还比较好懂。
代码
#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
int q=0;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
return q;
}
const int N=300005,M=1000005,mod=1000000007;
struct orzLaofu{//回文树
int last,n,SZ;
int a[N][26],fail[N],len[N],s[N],d[N],ff[N];
//d:等差数列的差值 ff:某一个等差数列开始的那个节点
void init() {fail[0]=1,len[1]=-1,s[0]=-1,SZ=1;}
int find(int x,int kn) {
int js=0;
while(s[kn-len[x]-1]!=s[kn]) x=fail[x];
return x;
}
void ins(int t) {
s[++n]=t;int now=find(last,n);
if(!a[now][t]) {
fail[++SZ]=a[find(fail[now],n)][t];
a[now][t]=SZ,len[SZ]=len[now]+2;
d[SZ]=len[SZ]-len[fail[SZ]];
ff[SZ]=(d[fail[SZ]]==d[SZ]?ff[fail[SZ]]:SZ);
}
last=a[now][t];
}
int h[N],ne[N],to[N],in[N],out[N],tot,ti;//建立fail树并获得dfs序
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
void build_fail_tree() {
for(RI i=0;i<=SZ;++i) h[i]=-1;
for(RI i=0;i<=SZ;++i) if(i!=1) add(fail[i],i);
}
void dfs(int x) {
in[x]=++ti;
for(RI i=h[x];i!=-1;i=ne[i]) dfs(to[i]);
out[x]=ti;
}
}T;
int n,m,res;
char s[N];int L[M],ne[M],ans[M],h[N];
struct orzCai{//线段树
int mx[N<<2];
void chan(int x,int s,int t,int i,int num) {
if(s==t) {mx[i]=num;return;}
int mid=(s+t)>>1;
if(x<=mid) chan(x,s,mid,i<<1,num);
else chan(x,mid+1,t,(i<<1)|1,num);
mx[i]=max(mx[i<<1],mx[(i<<1)|1]);
}
int query(int l,int r,int s,int t,int i) {
if(l<=s&&t<=r) return mx[i];
int mid=(s+t)>>1,re=0;
if(l<=mid) re=query(l,r,s,mid,i<<1);
if(mid+1<=r) re=max(re,query(l,r,mid+1,t,(i<<1)|1));
return re;
}
}QvQ;
struct orzBoshi{//树状数组,利用差分实现区间修改单点查询
int sum[N];
int lowbit(int x) {return x&(-x);}
void add(int x,int num) {while(x<=n) sum[x]+=num,x+=lowbit(x);}
int query(int x) {
int re=0;
while(x) re+=sum[x],x-=lowbit(x);
return re;
}
}QAQ;
int qm(int x) {return x>=mod?x-mod:x;}
int main()
{
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
int k;
n=read(),m=read(),scanf("%s",s+1);
T.init();
for(RI i=1;i<=n;++i) T.ins(s[i]-'a');
T.build_fail_tree(),T.dfs(1);
for(RI i=1;i<=m;++i)//用链式前向星储存询问
L[i]=read(),k=read(),ne[i]=h[k],h[k]=i;
for(RI i=1,x=1;i<=n;++i) {
x=T.a[T.find(x,i)][s[i]-'a'];
for(RI j=x;j;j=T.fail[T.ff[j]]) {//在树状数组上搞对答案的影响
QAQ.add(max(1,QvQ.query(T.in[j],T.out[j],1,T.ti,1)-T.len[j]+2),1);
QAQ.add(i-T.len[T.ff[j]]+2,-1);
}
QvQ.chan(T.in[x],1,T.ti,1,i);
for(RI j=h[i];j;j=ne[j]) ans[j]=QAQ.query(L[j]);
}
for(RI i=1;i<=m;++i) res=qm(res+1LL*ans[i]*i%mod);
printf("%d\n",res);
return 0;
}