[HN省队集训Day2]有趣的字符串题 回文树+线段树+树状数组

题目描述

白兔有一个长度为n的字符串。
白云有m个询问,每个询问会询问一段区间的本质不同回文子串个数。
(虽然原题没说,但是字符串只包含小写字母)

输入描述

第一行两个整数n, m。
接下来一行一个长度为n的字符串。
接下来m行,每行两个整数l, r。

输出描述

为了避免输出占用太多运行时间,你只需要输出 i = 1 m a n s i i mod 10 9 + 7 即可。

数据范围

n 300000 , m 10 6

解题思路

有一个结论:

一个字符串的所有回文后缀的长度,可以形成k个等差数列,k是log级的。
考虑前R个字符组成的字符串,对于一个等差数列,假设组成这个等差数列的回文串,最短的叫a,最长的叫b。对于右端点是R的询问,左端点在[b上一次出现的位置末尾+1,a的开头]这一段区间的话,ans要+1。

这么描述有点抽象,用图像表述就是:
灵魂画手litble
什么?你要证明?去看吉老师论文去

这样一来就比较好办,首先建立一棵回文树,那么等差数列就是一些顺着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;
}

猜你喜欢

转载自blog.csdn.net/litble/article/details/80765636
今日推荐