LOJ2720 「NOI2018」你的名字

你的名字

题目背景

实力强大的小A 被选为了ION2018 的出题人,现在他需要解决题目的命名问题。

题目描述

小A 被选为了ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外的工作都做好了。

由于ION 已经举办了很多届,所以在题目命名上也是有规定的,ION 命题手册规定:每年由命题委员会规定一个小写字母字符串,我们称之为那一年的命名串,要求每道题的名字必须是那一年的命名串的一个非空连续子串,且不能和前一年的任何一道题目的名字相同。

由于一些特殊的原因,小A 不知道ION2017 每道题的名字,但是他通过一些特殊手段得到了ION2017 的命名串,现在小A 有Q 次询问:每次给定ION2017 的命名串和ION2018 的命名串,求有几种题目的命名,使得这个名字一定满足命题委员会的规定,即是ION2018 的命名串的一个非空连续子串且一定不会和ION2017 的任何一道题目的名字相同。

由于一些特殊原因,所有询问给出的ION2017 的命名串都是某个串的连续子串,详细可见输入格式。

输入输出格式

输入格式:

第一行一个字符串S ,之后询问给出的ION2017 的命名串都是S 的连续子串。 第二行一个正整数Q,表示询问次数。 接下来Q 行,每行有一个字符串T 和两个正整数$l,r$,表示询问如果ION2017 的 命名串是$S [l..r]$,ION2018 的命名串是T 的话,有几种命名方式一定满足规定。

输出格式:

输出Q 行,第i 行一个非负整数表示第i 个询问的答案。

输入输出样例

输入样例#1: 复制
scbamgepe
3
smape 2 7
sbape 3 8
sgepe 1 9
输出样例#1: 复制
12
10
4

说明

测试点 $|S|\leq$ $Q\leq $ $\sum |T|\leq $ 其他限制
1 $200$ $200$ $40000$ $|T|\leq 200$
2 $1000$ $200$ $40000$ $|T|\leq 200$
3 $1000$ $200$ $40000$ $|T|\leq 200$
4 $1000$ $200$ $5 \times 10^5$
5 $1000$ $200$ $5 \times 10^5$
6 $5 \times 10^5$ $1$ $5 \times 10^5$
7 $5 \times 10^5$ $1$ $5 \times 10^5$
8 $10^5$ $10^5$ $2 \times 10^5$
9 $10^5$ $10^5$ $2 \times 10^5$ 字符串随机
10 $2 \times 10^5$ $10^5$ $4 \times 10^5$
11 $2 \times 10^5$ $10^5$ $4 \times 10^5$ 字符串随机
12 $3 \times 10^5$ $10^5$ $6 \times 10^5$
13 $3 \times 10^5$ $10^5$ $6 \times 10^5$ 字符串随机
14 $4 \times 10^5$ $10^5$ $8 \times 10^5$
15 $4 \times 10^5$ $10^5$ $8 \times 10^5$ 字符串随机
16 $5 \times 10^5$ $10^5$ $10^6$
17 $5 \times 10^5$ $10^5$ $10^6$ 字符串随机
18 $2 \times 10^5$ $10^5$ $10^6$
19 $3 \times 10^5$ $10^5$ $10^6$
20 $4 \times 10^5$ $10^5$ $10^6$
21 $5 \times 10^5$ $10^5$ $10^6$
22 $5 \times 10^5$ $10^5$ $10^6$
23 $5 \times 10^5$ $10^5$ $10^6$
24 $5 \times 10^5$ $10^5$ $10^6$
25 $5 \times 10^5$ $10^5$ $10^6$

对于前17个测试点的所有询问有$l=1,r=|S|$

对于所有数据,保证 $1\leq l \leq r \leq |S|$,$1\leq |T|\leq 5 \times 10^5$

题解

参照Mangoyang的题解。

补集转换一步,问题变成求 T 与 S[li:ri] 的本质不同的公共子串数,考虑让 T 在 S 的 sam 上匹配,双指针找出每一个前缀能在 S[li:ri] 中能匹配上的后缀长度 len[i],然后在 T 的 sam 上统计答案,对于每一个节点随便找一个出现的前缀,拿这个前缀的 [0,len[i]] 和其所能表示的字符串长度区间取交集即可。

求 len[i] 可以先找到第一个能接收当前字符 c 的节点,然后不断删去首字母,直到能在 [li:ri] 放下,也就是找到当前匹配节点的 right 集合,判断一段区间内是否有元素,这个用随便维护一下就好了,线段树合并蛮好写的。

时间复杂度\(O((|S|+\sum|T|)\log |S|)\)

#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
    rg T data=0,w=1;rg char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-') w=-w;
    for(;isdigit(ch);ch=getchar()) data=data*10+ch-'0';
    return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;
using namespace std;

co int N=2e6;
char s[N];
int res[N],n,q;
// Interval Tree
namespace T{
    int siz[N*25],lc[N*25],rc[N*25],tot;
    void insert(int&x,int l,int r,int p){
        if(!x) x=++tot;
        ++siz[x];
        if(l==r) return;
        int mid=l+r>>1;
        if(p<=mid) insert(lc[x],l,mid,p);
        else insert(rc[x],mid+1,r,p);
    }
    int merge(int x,int y){
        if(!x||!y) return x+y;
        int o=++tot;
        siz[o]=siz[x]+siz[y];
        lc[o]=merge(lc[x],lc[y]);
        rc[o]=merge(rc[x],rc[y]);
        return o;
    }
    int query(int x,int l,int r,int ql,int qr){
        if(!x) return 0;
        if(ql<=l&&r<=qr) return siz[x];
        int mid=l+r>>1;
        if(qr<=mid) return query(lc[x],l,mid,ql,qr);
        if(ql>mid) return query(rc[x],mid+1,r,ql,qr);
        return query(lc[x],l,mid,ql,qr)+query(rc[x],mid+1,r,ql,qr);
    }
}
// Suffix Automaton
vector<int> vec[N];
namespace S1{ // S
    vector<int> e[N];
    int last=1,tot=1;
    int ch[N][26],fa[N],len[N],rt[N];
    void extend(int c,int po){
        int p=last,cur=last=++tot;
        T::insert(rt[cur],1,n,po);
        for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=cur;
        if(!p) fa[cur]=1;
        else{
            int q=ch[p][c];
            if(len[q]==len[p]+1) fa[cur]=q;
            else{
                int clone=++tot;
                memcpy(ch[clone],ch[q],sizeof ch[q]);
                fa[clone]=fa[q],len[clone]=len[p]+1;
                fa[cur]=fa[q]=clone;
                for(;ch[p][c]==q;p=fa[p]) ch[p][c]=clone;
            }
        }
    }
    void dfs(int u){
        for(int i=0;i<e[u].size();++i)
            dfs(e[u][i]),rt[u]=T::merge(rt[u],rt[e[u][i]]);
    }
    void build_tree(){
        for(int i=2;i<=tot;++i) e[fa[i]].push_back(i);
        dfs(1);
    }
    void solve(char*s,int L,int R){
        int length=strlen(s+1);
        for(int i=1,p=1,now=0;i<=length;++i){
            int c=s[i]-'a';
            while(!ch[p][c]&&p) p=fa[p],now=len[p];
            if(!p) {p=1,now=0;continue;}
            p=ch[p][c],++now;
            while(p>1){
                if(T::query(rt[p],1,n,L+now-1,R)) break;
                if(--now==len[fa[p]]) p=fa[p];
            }
            if(p==1) continue;
            for(int j=0;j<vec[i].size();++j)
                res[vec[i][j]]=max(res[vec[i][j]],now);
        }
    }
}
namespace S2{ // T
    int last,tot;
    int ch[N][26],fa[N],len[N];
    void clear(){
        for(int i=1;i<=tot;++i){
            fa[i]=len[i]=res[i]=0;
            memset(ch[i],0,sizeof ch[i]);
        }
        last=tot=1;
    }
    void extend(int c,int po){
        int p=last,cur=last=++tot;
        len[cur]=len[p]+1,vec[po].push_back(cur);
        for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=cur;
        if(!p) fa[cur]=1;
        else{
            int q=ch[p][c];
            if(len[q]==len[p]+1) fa[cur]=q;
            else{
                int clone=++tot;
                memcpy(ch[clone],ch[q],sizeof ch[q]);
                fa[clone]=fa[q],len[clone]=len[p]+1,vec[po].push_back(clone);
                fa[cur]=fa[q]=clone;
                for(;ch[p][c]==q;p=fa[p]) ch[p][c]=clone;
            }
        }
    }
    void solve(){
        ll ans1=0,ans2=0;
        for(int i=1;i<=tot;++i){
            if(res[i]>len[fa[i]]) ans2+=min(res[i],len[i])-len[fa[i]];
            ans1+=len[i]-len[fa[i]];
        }
        printf("%lld\n",ans1-ans2);
    }
}
int main(){
//  freopen("name.in","r",stdin),freopen("name.out","w",stdout);
    scanf("%s",s+1),n=strlen(s+1);
    for(int i=1;i<=n;++i) S1::extend(s[i]-'a',i);
    S1::build_tree();
    read(q);
    for(int m,L,R;q--;){
        scanf("%s",s+1),m=strlen(s+1);
        read(L),read(R);
        for(int i=1;i<=m;++i) vec[i].clear();
        S2::clear();
        for(int i=1;i<=m;++i) S2::extend(s[i]-'a',i);
        S1::solve(s,L,R),S2::solve();
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/autoint/p/10907556.html