【BZOJ5417】你的名字(NOI2018)-后缀自动机+主席树

测试地址:你的名字
做法:本题需要用到后缀自动机+主席树。
首先考虑 l=1,r=|S| l = 1 , r = | S | 的情况。考虑 T T 的每个前缀的贡献,我们需要找到它最短的没在S中出现过,而且没在 T T 的前面部分出现过的后缀,这样包含它的所有后缀就都是合法的贡献了。显然这个后缀的长度等同于,在S中出现过或者在 T T 的前面部分出现过的最长的后缀的长度加1。因此我们分开考虑两种情况。对于在 T T 的前面部分出现过的最长后缀,可以通过对T建后缀自动机,利用后缀树的性质,决定每个点所代表的串在哪个前缀中第一次出现,DFS一遍统计贡献即可。而对于在 S S 中出现过的最长后缀,显然就是用T S S 的后缀自动机中匹配,匹配的长度就是最长后缀长度。这样我们就能解决68分的部分了。
再考虑拓展到l,r任意的情况。 l,r l , r 的变化对上面的做法有影响的只有在 S S 自动机中的匹配。其实没什么大的变化,只不过我们每次需要判断走到的点合不合法。具体地,我们是需要判断,当前匹配长度为d的情况下,在末尾添加一个字符 c c 后,能不能走到下一个点。
首先如果目前的匹配点完全没有字符c的指针,显然就不能继续走,要根据后缀链接跳回去再进行试探。如果有字符 c c 的指针,也不一定就能走,因为那个字符串虽然出现在了S中,但不一定出现在指定的区间中。这时候要判断在某个区间中存不存在当前字符串。根据后缀自动机的性质,每个点有一个 Right R i g h t 集合,即该点所代表的字符串可能出现的右端点的集合,而这个 Right R i g h t 集合显然就是这个点在后缀树上的子树中所包含的所有前缀节点。我们要判断的是, Right R i g h t 集合中存不存在一个 x x ,使得xr,而且 x(d+1)+1l x − ( d + 1 ) + 1 ≥ l ,即 l+dxr l + d ≤ x ≤ r 。如果我们对后缀树维护一个DFS序,那么这就变成了在区间中询问存不存在一个权值在某区间内的数,这就是主席树的经典应用了,所以我们使用主席树来判断能不能往下走。
还有一点要注意,如果当前不能走,并不是直接通过后缀链接回退到上面的点,因为当前点还是可能有很多长度不一的后缀的,那么现在的问题就是在长度集合 [dl,dr] [ d l , d r ] 中,最大的使得权值区间 [l+d,r] [ l + d , r ] 在上述算法的询问中合法的 d d ,这个d就是我们所回退到的串的长度,这时候再往下走就行了。这个东西可以在主席树上分治得到(需要做点处理,详见代码)。当然,如果找不到这个点,就沿着后缀链接退回上一个点。
由于回退和前进的次数都是线性的,所以以上算法的时间复杂度是 O(|T|log|S|) O ( | T | log ⁡ | S | ) ,可以通过此题。
总的来说,这道题目考察了后缀自动机本身作为匹配工具的性质,又考察了作为辅助的后缀链接,也就是后缀树的性质,还考察了将高级数据结构——主席树维护DFS序的方法在后缀树上灵活运用的能力,思路自然,码量适中,给出题人点赞(我是认真的)。
(但据说已经是套路题了……)
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
const int inf=1000000000;
int n,m,tot,last,totp,first[2000010],tote;
int ch[2000010][26],pre[2000010],len[2000010],vis[2000010]={0};
int tim=0,in[1000010],out[1000010],mx[500010];
int totseg=0,rt[500010]={0},seg[10000010]={0},segch[10000010][2]={0};
int qlen,ql,qr;
char s[500010];
struct edge
{
    int v,next;
}e[2000010];

void extend(int rt,int c)
{
    int p,q,np,nq;
    p=last;
    np=++tot;
    len[np]=len[p]+1;
    for(int i=0;i<26;i++)
        ch[np][i]=0;
    while(p&&!ch[p][c]) ch[p][c]=np,p=pre[p];

    if (p)
    {
        q=ch[p][c];
        if (len[p]+1==len[q]) pre[np]=q;
        else
        {
            nq=++tot;
            for(int i=0;i<26;i++)
                ch[nq][i]=ch[q][i];
            len[nq]=len[p]+1;
            pre[nq]=pre[q];
            pre[q]=pre[np]=nq;
            while(p&&ch[p][c]==q) ch[p][c]=nq,p=pre[p];
        }
    }
    else pre[np]=rt;

    last=np;
}

void insert(int a,int b)
{
    e[++tote].v=b;
    e[tote].next=first[a];
    first[a]=tote;
}

void build(int start,int n)
{
    tot=last=start;
    pre[start]=len[start]=0;
    len[0]=-1;
    for(int i=0;i<26;i++)
        ch[start][i]=0;
    for(int i=1;i<=n;i++)
    {
        extend(start,s[i]-'a');
        vis[last]=i;
    }

    tote=0;
    for(int i=start;i<=tot;i++)
        first[i]=0;
    for(int i=start+1;i<=tot;i++)
        insert(pre[i],i);
}

void pushup(int no)
{
    seg[no]=seg[segch[no][0]]+seg[segch[no][1]];
}

void insert(int &v,int last,int l,int r,int x)
{
    v=++totseg;
    seg[v]=seg[last];
    segch[v][0]=segch[last][0];
    segch[v][1]=segch[last][1];
    if (l==r) {seg[v]++;return;}
    int mid=(l+r)>>1;
    if (x<=mid) insert(segch[v][0],segch[last][0],l,mid,x);
    else insert(segch[v][1],segch[last][1],mid+1,r,x);
    pushup(v);
}

void dfs1(int v)
{
    in[v]=tim+1;
    if (vis[v])
    {
        tim++;
        insert(rt[tim],rt[tim-1],1,n,vis[v]);
    }
    for(int i=first[v];i;i=e[i].next)
        dfs1(e[i].v);
    out[v]=tim;
}

void dfs2(int v)
{
    if (!vis[v]) vis[v]=inf;
    for(int i=first[v];i;i=e[i].next)
    {
        dfs2(e[i].v);
        vis[v]=min(vis[v],vis[e[i].v]);
    }
    for(int i=first[v];i;i=e[i].next)
        if (vis[v]!=vis[e[i].v])
            mx[vis[e[i].v]]=len[v];
    if (v==totp+1) mx[vis[v]]=0;
}

int querysum(int vr,int vl,int l,int r,int s,int t)
{
    if (l>=s&&r<=t) return seg[vr]-seg[vl];
    int mid=(l+r)>>1,ans=0;
    if (s<=mid) ans+=querysum(segch[vr][0],segch[vl][0],l,mid,s,t);
    if (t>mid) ans+=querysum(segch[vr][1],segch[vl][1],mid+1,r,s,t);
    return ans;
}

int querylast(int vr,int vl,int l,int r,int s,int t)
{
    if (seg[vr]-seg[vl]==0) return 0;
    if (l==r) return l;
    int mid=(l+r)>>1,ans=0;
    if (t>mid) ans=querylast(segch[vr][1],segch[vl][1],mid+1,r,s,t);
    if (ans) return ans;
    if (s<=mid) ans=querylast(segch[vr][0],segch[vl][0],l,mid,s,t);
    return ans;
}

void solve()
{
    int now=1,d=0,l,r;
    long long ans=0;
    scanf("%d%d",&l,&r);
    for(int i=1;i<=qlen;i++)
    {
        int c=s[i]-'a';
        while(now)
        {
            if (!ch[now][c]) {now=pre[now],d=len[now];continue;}
            int s=len[pre[now]]+1,t=min(d,r-l);
            if (s>t||l+s>r) {now=pre[now],d=len[now];continue;}
            int rta=rt[out[ch[now][c]]],rtb=rt[in[ch[now][c]]-1];
            if (l+t+1>r||querysum(rta,rtb,1,n,l+t+1,r)==0)
            {
                int x=querylast(rta,rtb,1,n,l+s,l+t);
                if (!x) {now=pre[now],d=len[now];continue;}
                now=ch[now][c],d=x-l+1;break;
            }
            else {now=ch[now][c],d=t+1;break;}
        }
        if (!now) now=1,d=0;
        ans+=(long long)(i-max(mx[i],d));
    }
    printf("%lld\n",ans);
}

int main()
{
    scanf("%s",s+1);
    s[0]='#';
    n=strlen(s)-1;
    build(1,n);
    totp=tot;

    dfs1(1);

    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%s",s+1);
        qlen=strlen(s)-1;
        for(int j=totp+1;j<=totp+(qlen<<1)+1;j++)
            vis[j]=0;
        build(totp+1,qlen);

        dfs2(totp+1);
        solve();
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值