[NOI2018]你的名字(后缀自动机+线段树合并)
[NOI2018]你的名字(后缀自动机+线段树合并)
题面
给出一个字符串\(S\),有\(q\)组询问,每次询问给出一个字符串\(T\)和整数\(l,r\).问能从\(T\)中选出多少个本质不同的子串,满足这个子串在\(S\)的区间\([l,r]\)没有出现过。
\(|S| \leq 5 \times 10^5,q \leq 10^5,\sum|T| \leq 5 \times 10^5\)
分析
AC这道题的一瞬间,我感觉和后缀自动姬合二为一,达到了生命的大和谐。
简化问题
考虑一个简化的问题,只考虑\(T\)和整个\(S\)串的答案。
把\(S\)和\(T\)都建出后缀自动机,记为\(M_1\)和\(M_2\).然后把\(T\)串放到\(M_1\)上匹配,记录当前节点\(x_1\)和匹配长度\(matlen\),然后还要记录在\(M_2\)上走到的节点\(x_2\). 对于节点\(x_2\),它贡献的本质不同的串的长度应该在\([len(link(x_2))+1,len(x_2)]\),个数就是区间长度. 但是还要考虑到如果不能与\(S\)匹配,所以如果\(matlen\)落在\([len(link(x_2))+1,len(x_2)]\),贡献的本质的长度范围应该是\([matlen+1,len(x_2)]\).
因此我们对\(M_2\)的每个节点维护一个值\(mlen\)表示该节点与\(S\)的最大匹配长度。每次用matlen去更新答案。更新方法是沿着link树往上跳,设跳到的节点为\(y\),如果\(matlen<len(link(y)+1)\),那么mlen不变,增加的子串个数为\(mlen-len(link(y))\).否则更新\(mlen\)为\(matlen\),增加的子串个数为\(matlen-len(link(y))\).
正解
如何求区间\(S[l,r]\)内的字符与\(T\)匹配的结果呢?我们可以用到\(right\)集合。如果\(M_1\)的\(right\)集合中存在落在当前需要匹配的区间\([l+matlen,r]\)中,那么匹配长度\(matlen\)就可以+1. 否则也是沿着\(link\)往上跳即可。\(right\)集合可以用权值线段树维护,预处理的时候用线段树合并,查询的时候就是一个区间求和。
细节比较多,见代码注释
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define maxc 26
#define maxn 1000000
#define maxlogn 30
using namespace std;
int n,q;
char s[maxn+5],t[maxn+5];
struct segment_tree{
#define lson(x) (tree[x].ls)
#define rson(x) (tree[x].rs)
struct node{
int ls;
int rs;
int val;
}tree[maxn*maxlogn+5];
int ptr=0;
void push_up(int x){
tree[x].val=tree[lson(x)].val+tree[rson(x)].val;
}
void update(int &x,int upos,int l,int r){
x=++ptr;
if(l==r){
tree[x].val++;
return;
}
int mid=(l+r)>>1;
if(upos<=mid) update(lson(x),upos,l,mid);
else update(rson(x),upos,mid+1,r);
push_up(x);
}
int query(int x,int L,int R,int l,int r){
if(x==0||L>R) return 0;
if(L<=l&&R>=r) return tree[x].val;
int mid=(l+r)>>1;
int ans=0;
if(L<=mid) ans+=query(lson(x),L,R,l,mid);
if(R>mid) ans+=query(rson(x),L,R,mid+1,r);
return ans;
}
int merge(int x,int y){
if(!x||!y) return x+y;
int now=++ptr;//这样合并不会影响原来的两棵树,类似可持久化
tree[now].val=tree[x].val+tree[y].val;
lson(now)=merge(lson(x),lson(y));
rson(now)=merge(rson(x),rson(y));
return now;
}
#undef lson
#undef rson
}Tr;
struct SAM{
#define link(x) t[x].link
#define len(x) t[x].len
struct node{
int ch[maxc];
int link;
int len;
int mlen;//该位置代表的后缀向前最长的合法长度(没有和S匹配)
int root;//代表的right集合对应的线段树的根节点
void clear(){
//因为要多次重建自动机,做好初始化
for(int i=0;i<maxc;i++) ch[i]=0;
link=len=root=0;
}
}t[maxn*2+5];
const int root=1;
int last=root;
int ptr=1;
inline int size(){
return ptr;
}
void ini(){
last=root;
ptr=1;
t[root].clear();
}
int New(){
ptr++;
t[ptr].clear();
return ptr;
}
inline int delta(int x,int c){
return t[x].ch[c];//转移函数
}
void extend(int c,int pos){
int p=last,cur=New();
len(cur)=len(p)+1;
if(pos) Tr.update(t[cur].root,pos,1,n);//因为并不需要T的right集合,插入T的时候把pos设为0
while(p&&t[p].ch[c]==0){
t[p].ch[c]=cur;
p=link(p);
}
if(p==0) link(cur)=root;
else{
int q=t[p].ch[c];
if(len(p)+1==len(q)) link(cur)=q;
else{
int clo=New();
len(clo)=len(p)+1;
for(int i=0;i<maxc;i++) t[clo].ch[i]=t[q].ch[i];
link(clo)=link(q);
link(q)=link(cur)=clo;
while(p&&t[p].ch[c]==q){
t[p].ch[c]=clo;
p=link(p);
}
}
}
last=cur;
}
void topo_sort(){
static int in[maxn+5];
queue<int>q;
for(int i=1;i<=ptr;i++) in[link(i)]++;
for(int i=1;i<=ptr;i++) if(!in[i]) q.push(i);
while(!q.empty()){
int x=q.front();
q.pop();
if(link(x)==0) continue;
in[link(x)]--;
t[link(x)].root=Tr.merge(t[link(x)].root,t[x].root);
if(in[link(x)]==0) q.push(link(x));
}
}
}S1,S2;
long long calc(char *t,int l,int r){
int lent=strlen(t+1);
S2.ini();
for(int i=1;i<=lent;i++) S2.extend(t[i]-'a',0);
for(int i=1;i<=S2.size();i++) S2.t[i].mlen=S2.t[i].len;
int x1=S1.root,x2=S2.root;
int matlen=0;//S[l,r]与T的匹配长度
long long ans=0;
for(int i=1;i<=lent;i++){
int c=t[i]-'a';
x2=S2.delta(x2,c);
if(S1.delta(x1,c)&&Tr.query(S1.t[S1.delta(x1,c)].root,l+matlen,r,1,n)){
//如果S串的ch[x][c]对应的right集合中,有被当前需要匹配的范围[l+tot,r]包含的,就说明当前的T与S[l,r]匹配长度+1.
x1=S1.t[x1].ch[c];
matlen++;
}else{
while(x1&&!(S1.delta(x1,c)&&Tr.query(S1.t[S1.delta(x1,c)].root,l+matlen,r,1,n))){//不能匹配,跳link
if(matlen==0){
x1=0;
break;
}
matlen--;//减少匹配长度,尝试卡进区间内
if(matlen==S1.len(S1.link(x1))) x1=S1.link(x1);
}
if(!x1){
x1=S1.root;
matlen=0;
}else{
x1=S1.delta(x1,c);
matlen++;
}
}
for(int y=x2;y;y=S2.link(y)){
if(matlen>S2.len(S2.link(y))){//匹配位置在当前节点代表的串[len(link(x))+1,len(x)]内
ans+=S2.t[y].mlen-matlen;
S2.t[y].mlen=matlen;
break;//此时对祖先节点已经没有影响,可以break
}else{//否则这整个区间都是合法的
ans+=S2.t[y].mlen-S2.len(S2.link(y));
S2.t[y].mlen=S2.len(S2.link(y));//这一部分的贡献已经计算过了,要移到前面
}
}
// printf("i=%d x1=%d x2=%d,ans=%lld\n",i,x1,x2,ans);
}
// printf("----\n");
return ans;
}
int main(){
#ifndef LOCAL
freopen("name.in","r",stdin);
freopen("name.out","w",stdout);
#endif
int l,r;
scanf("%s",s+1);
n=strlen(s+1);
S1.ini();
for(int i=1;i<=n;i++) S1.extend(s[i]-'a',i);
S1.topo_sort();
scanf("%d",&q);
for(int i=1;i<=q;i++){
scanf("%s",t+1);
scanf("%d %d",&l,&r);
printf("%lld\n",calc(t,l,r));
}
}
/*
abc
1
cad 2 3
*/