[NOI2018] 你的名字
题目背景
实力强大的小 A 被选为了 ION2018 的出题人,现在他需要解决题目的命名问题。
题目描述
小 A 被选为了 ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外的工作都做好了。
由于 ION 已经举办了很多届,所以在题目命名上也是有规定的,ION 命题手册规定:每年由命题委员会规定一个小写字母字符串,我们称之为那一年的命名串,要求每道题的名字必须是那一年的命名串的一个非空连续子串,且不能和前一年的任何一道题目的名字相同。
由于一些特殊的原因,小 A 不知道 ION2017 每道题的名字,但是他通过一些特殊手段得到了 ION2017 的命名串,现在小 A 有 Q Q Q 次询问:每次给定 ION2017 的命名串和 ION2018 的命名串,求有几种题目的命名,使得这个名字一定满足命题委员会的规定,即是 ION2018 的命名串的一个非空连续子串且一定不会和 ION2017 的任何一道题目的名字相同。
由于一些特殊原因,所有询问给出的 ION2017 的命名串都是某个串的连续子串,详细可见输入格式。
输入格式
第一行一个字符串 S S S ,之后询问给出的 ION2017 的命名串都是 S S S 的连续子串。
第二行一个正整数 Q Q Q,表示询问次数。
接下来 Q Q Q 行,每行有一个字符串 T T T 和两个正整数 l , r l,r l,r,表示询问如果 ION2017 的命名串是 S l … r S_{l\ldots r} Sl…r,ION2018 的命名串是 T T T 的话,有几种命名方式一定满足规定。
输出格式
输出 Q Q Q 行,第 i i i 行一个非负整数表示第 i i i 个询问的答案。
样例 #1
样例输入
scbamgepe
3
smape 2 7
sbape 3 8
sgepe 1 9
样例输出
12
10
4
给定一个模板串 S S S,多组询问,每次给出一个询问字符串 T T T和一个区间 ( l , r ) (l,r) (l,r)
要求你输出 T T T有多少个本质不同的子串,满足这个子串没有在 S S S的 ( l , r ) (l,r) (l,r)这段区间当中出现。
直接去求没出现的子串数目 ( F 1 ) (F_1) (F1)肯定没法做,我们可以先求 T T T在 ( l , r ) (l,r) (l,r)中出现的子串数目 ( F 2 ) (F_2) (F2),然后用总的子串数目 ( F ) (F) (F)来减就好了,也就是 F 1 = F − F 2 F_1=F-F_2 F1=F−F2。
总的子串数目还是比较好算的,直接对每一个 T T T建立 S A M SAM SAM,那么 F = ∑ ( l [ i ] − l [ f a i ] ) F=\sum (l[i]-l[fa_i]) F=∑(l[i]−l[fai]),关键就是怎么算 F 2 F_2 F2。
case1: l = 1 , r = ∣ S ∣ l=1,r=|S| l=1,r=∣S∣
我们首先不考虑 l l l和 r r r的限制,当我们拿到一个 T T T串之后,先求出以 T T T中的每一个位置为结尾且能与 S S S匹配的最大长度。
比如 S S S为 a a b a b aabab aabab, T T T为 a b b abb abb,那么 T T T以第一个位置 a a a为结尾的最大匹配长度为1 ( a ) (a) (a), T T T以第二个位置 b b b为结尾的最大匹配长度为2 ( a b ) (ab) (ab), T T T以第三个位置 b b b为结尾的最大匹配长度为1 ( b ) (b) (b)。
这个问题可以通过把 T T T串直接在 S S S的 S A M SAM SAM上跑匹配,然后维护匹配的串的长度来解决。
得到了 T T T每个位置的最大匹配长度之后,就可以沿着 T T T的 p a r e n t parent parent树去更新祖先结点,显然最大的匹配长度不能大于这个点本身所能表示的字符串的最大长度,所以每次向上更新的时候还要和自己的 l e n len len取 m i n min min。
但是如果暴力跳 f a t h e r father father显然不可行,我们只需要对每一个位置对应的 S A M SAM SAM上的结点打一个 t a g tag tag,最后 d f s dfs dfs一次合并即可。
我们设 t a g x tag_x tagx表示在 T T T的 S A M SAM SAM上的 x x x点能够匹配到 S S S的最大长度,那么有: F 2 = ∑ m a x ( 0 , t a g i − l [ f a i ] ) F_2=\sum max(0,tag_i-l[fa_i]) F2=∑max(0,tagi−l[fai]),因此: F 1 = F − F 2 = ∑ ( l [ i ] − l [ f a i ] ) − ∑ m a x ( 0 , t a g i − l [ f a i ] ) F_1=F-F_2=\sum (l[i]-l[fa_i])-\sum max(0,tag_i-l[fa_i]) F1=F−F2=∑(l[i]−l[fai])−∑max(0,tagi−l[fai])。
讨论 t a g i tag_i tagi和 l [ f a i ] l[fa_i] l[fai]的大小,不难推出: F 1 = ∑ ( l [ i ] − m a x ( l [ f a i ] , t a g i ) ) F_1=\sum (l[i]-max(l[fa_i],tag_i)) F1=∑(l[i]−max(l[fai],tagi)),这样只需要在 T T T串的 p a r e n t parent parent树上 d f s dfs dfs一遍即可。
case2: l , r l,r l,r无限制
有了 l l l和 r r r的限制之后,我们求最大匹配长度的时候就不能只考虑能否匹配的问题了,还需要考虑当前匹配到的 S S S串 S A M SAM SAM上的点所代表的串是否在 l , r l,r l,r的区间中。
如果一个串要满足在 [ l , r ] [l,r] [l,r]中,那么不仅要保证右端点在区间中,也要保证左端点在区间中,因此,如果当前串长为 l e n len len,那么结束位置满足 [ l + l e n − 1 , r ] [l+len-1,r] [l+len−1,r]这样的条件,因此我们只需要关心每一个 S A M SAM SAM的结点的子树中有没有符合要求的点,由于是维护子树信息,可以直接 d f s dfs dfs把子树搞成一个区间问题,然后用主席树来限制左右端点即可。
注意,这种情况下失配是不能直接跳 f a t h e r father father,而是应该 l e n − − len-- len−−,因为在这些等价类中可能含有长度较小的且能够匹配的串,直到 l e n len len减到与 l e n [ f a i ] len[fa_i] len[fai]相同的时候,再跳father。
#include<bits/stdc++.h>
#define maxn 2000005
#define int long long
using namespace std;
int read()
{
int x=1,res=0;
char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')
x=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
res=res*10+(c-'0');
c=getchar();
}
return res*x;
}
struct edge{
int next,to;
};
int tag[maxn],ans,dfn[maxn],cnt;
struct SAM{
int id[maxn],pos[maxn],tot=1,lt=1,num,l[maxn],ch[maxn][26],sz[maxn],f[maxn],last[maxn];
edge g[maxn];
void init(int len){
for(int i=0;i<=len;i++){
id[i]=0;pos[i]=0;f[i]=0;pos[i]=0;
last[i]=0;l[i]=0;sz[i]=0;tag[i]=0;
for(int j=0;j<26;j++) ch[i][j]=0;
}
tot=lt=1;num=0;
}
void insert(int c,int i){
int v=++tot,u=lt;lt=tot;pos[tot]=i;id[i]=tot;
l[v]=l[u]+1;
while(u&&!ch[u][c]) {
ch[u][c]=v;u=f[u];}
if(!u) {
f[v]=1;return;}
int x=ch[u][c];
if(l[x]==l[u]+1) {
f[v]=x;return;}
int y=++tot;pos[y]=pos[x];
l[y]=l[u]+1;f[y]=f[x];f[x]=f[v]=y;
memcpy(ch[y],ch[x],sizeof(ch[x]));
while(u&&ch[u][c]==x) {
ch[u][c]=y;u=f[u];}
}
void add(int from,int to)
{
g[++num].next=last[from];
g[num].to=to;
last[from]=num;
}
void dfs1(int x)
{
dfn[x]=++cnt;sz[x]=1;
for(int i=last[x];i;i=g[i].next)
{
int v=g[i].to;
dfs1(v);
sz[x]+=sz[v];
}
}
void dfs(int x)
{
for(int i=last[x];i;i=g[i].next)
{
int v=g[i].to;
dfs(v);
tag[x]=max(tag[x],min(l[x],tag[v]));
sz[x]+=sz[v];
}
if(x!=1)
ans+=l[x]-max(l[f[x]],tag[x]);
}
}sam,sam1;
struct tr{
int l,r,val;
}t[maxn*10];
int tot,root[maxn];
void modify(int &k,int l,int r,int x,int val,int last){
if(!k) k=++tot;
if(l==r){
t[k].val+=val;
return;
}
int mid=(l+r)>>1;
if(x<=mid) {
if(!t[k].r) t[k].r=t[last].r;
modify(t[k].l,l,mid,x,val,t[last].l);
}
else {
if(!t[k].l) t[k].l=t[last].l;
modify(t[k].r,mid+1,r,x,val,t[last].r);
}
t[k].val=t[t[k].l].val+t[t[k].r].val;
}
int query(int k,int l,int r,int x,int y){
if(x<=l&&r<=y) return t[k].val;
int mid=(l+r)>>1,ans=0;
if(x<=mid) ans+=query(t[k].l,l,mid,x,y);
if(mid+1<=y) ans+=query(t[k].r,mid+1,r,x,y);
return ans;
}
char a[maxn],b[maxn];
signed main()
{
cin>>a+1;
int len=strlen(a+1);
for(int i=1;i<=len;i++) sam.insert(a[i]-'a',i);
for(int i=2;i<=sam.tot;i++) sam.add(sam.f[i],i);
sam.dfs1(1);
for(int i=1;i<=len;i++){
int u=dfn[sam.id[i]];
modify(root[i],1,cnt,u,1,root[i-1]);
}
int q=read();
while(q--){
cin>>b+1;int l=read(),r=read();
int L=strlen(b+1); sam1.init(2*L+5);
for(int i=1;i<=L;i++) sam1.insert(b[i]-'a',i);
for(int i=2;i<=sam1.tot;i++) sam1.add(sam1.f[i],i);
if(l==1&&r==len){
int u=1,ll=0;ans=0;
for(int i=1;i<=L;i++){
int c=b[i]-'a';
while(u>1&&!sam.ch[u][c]){
u=sam.f[u];
ll=sam.l[u];
}
if(sam.ch[u][c]){
u=sam.ch[u][c];
ll++;
}
tag[sam1.id[i]]=ll;
}
sam1.dfs(1);
cout<<ans<<endl;
}
else{
int u=1,ll=0;ans=0;
for(int i=1;i<=L;i++){
int c=b[i]-'a';
int v=sam.ch[u][c];
int x=dfn[v],y=dfn[v]+sam.sz[v]-1;
while(u>1&&(!sam.ch[u][c]||query(root[r],1,cnt,x,y)-query(root[l+ll-1],1,cnt,x,y)<1)){
ll--;
if(ll==sam.l[sam.f[u]]) u=sam.f[u];
v=sam.ch[u][c];
x=dfn[v],y=dfn[v]+sam.sz[v]-1;
}
if(sam.ch[u][c]){
int v=sam.ch[u][c];
int x=dfn[v],y=dfn[v]+sam.sz[v]-1;
if(query(root[r],1,cnt,x,y)-query(root[l+ll-1],1,cnt,x,y)>=1)
{
u=v;
ll++;
}
}
tag[sam1.id[i]]=ll;
}
sam1.dfs(1);
cout<<ans<<endl;
}
}
return 0;
}