loj 2720 [NOI2018] 你的名字
有一个串 (S) , (Q) 次询问,每次给出一个串 (T) ,和 (l,r) .
求 (T) 中有多少非空本质不同子串没有在 (S[l cdots r]) 中出现过
(|S| le 5 imes 10^5, sum |T| le 10^6,1 le l le r le |S|)
Tutorial
https://dangxingyu.com/2018/10/30/noi2018-%E4%BD%A0%E7%9A%84%E5%90%8D%E5%AD%97/
https://www.cnblogs.com/cjyyb/p/10646874.html
考虑对于 (T) 建立SAM,想要对每个endpos等价类(节点)统计贡献.发现我们想要求出的,就是以其中 (T) 中每个位置作为结尾,在 (S[l cdots r]) 中出现过的最长后缀的长度.
考虑通过在 (S) 的SAM上遍历以求得 (Len_i) 表示 (T[1 cdots i]) 的最长的在 (S[l cdots r]) 中出现过的后缀的长度.
若 (l=1,r=n) ,那么可以维护当前节点 (u) ,当前长度 (now) .设当前加入字符为 (c=T_i) ,那么若存在 (ch_{u,c}) 则前进,否则跳到 (link_u)
对于一般的情况,我们不仅要判断 (ch_{u,c}) 是否存在,还需要判断其代表的endpos等价类中出否出现过 ([l+now,r]) 中的位置.所以此时不能直接跳 (link) ,应该每次 (now) 减1.
endpos等价类可以用线段树合并维护.
时间复杂度 (O((|S| + sum |T|) log |S|))
Code
SAM中的link不是有序的.
线段树合并时,若还需要使用被合并的节点,那么需要新建一个节点返回.
#include <cassert>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#define debug(...) fprintf(stdout,__VA_ARGS__)
#define lson tree[u].ls,l,mid
#define rson tree[u].rs,mid+1,r
using namespace std;
template<class T> void read(T &x) {
x=0; int f=1,ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10-'0'+ch;ch=getchar();}
x*=f;
}
typedef long long ll;
const int sigma=26;
const int maxn=5e5+50,maxm=1e6+50;
const int maxnode=maxm<<1;
int n; char S[maxn];
int m; char T[maxm];
int Q;
int Len[maxm];
int root[maxn<<1];
namespace seg {
const int maxnode=maxn*100;
int all;
struct node {
int ls,rs;
} tree[maxnode];
void insert(int &u,int l,int r,int qp) {
if(!u) u=++all;
if(l==r) {
return;
}
int mid=(l+r)>>1;
if(qp<=mid) insert(lson,qp);
else insert(rson,qp);
}
int merge(int a,int b) {
if(!a||!b) return a+b;
int c=++all;
tree[c].ls=merge(tree[a].ls,tree[b].ls);
tree[c].rs=merge(tree[a].rs,tree[b].rs);
return c;
}
bool query(int u,int l,int r,int ql,int qr) {
if(ql>qr) return 0;
if(u==0) return 0;
if(l==ql&&r==qr) {
return 1;
}
int mid=(l+r)>>1;
if(qr<=mid) return query(lson,ql,qr);
else if(ql>mid) return query(rson,ql,qr);
else {
bool L=query(lson,ql,mid);
bool R=query(rson,mid+1,qr);
return L||R;
}
}
}
vector<int> adj[maxn<<1];
struct string_suffix_machine {
int las,all,len[maxnode],link[maxnode],endpos[maxnode],ch[maxnode][sigma];
inline int newnode() {
int u=++all;
memset(ch[u],0,sizeof(ch[u]));
return u;
}
void init() {
all=las=0,link[0]=-1,endpos[0]=-1;
memset(ch[0],0,sizeof(ch[0]));
}
void extend(int c,int pos) {
int cur=newnode(),p=las;
len[cur]=len[p]+1;
endpos[cur]=pos;
while(p!=-1&&!ch[p][c]) {
ch[p][c]=cur;
p=link[p];
}
if(p==-1) link[cur]=0;
else {
int q=ch[p][c];
if(len[q]==len[p]+1) link[cur]=q;
else {
int nq=newnode();
len[nq]=len[p]+1;
link[nq]=link[q];
endpos[nq]=pos;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
link[cur]=link[q]=nq;
while(p!=-1&&ch[p][c]==q) {
ch[p][c]=nq;
p=link[p];
}
}
}
las=cur;
}
void dfs(int u) {
if(u!=0) seg::insert(root[u],0,n-1,endpos[u]);
for(int i=0;i<adj[u].size();++i) {
int v=adj[u][i];
dfs(v);
root[u]=seg::merge(root[u],root[v]);
}
}
void build() {
for(int i=1;i<=all;++i) {
adj[link[i]].push_back(i);
}
dfs(0);
}
void travel(char *T,int m,int l,int r) {
--l,--r;
int u=0,now=0;
for(int i=0;i<m;++i) {
int c=T[i]-'a';
while(true) {
if(ch[u][c]&&seg::query(root[ch[u][c]],0,n-1,l+now,r)) {
u=ch[u][c],++now;
break;
}
else {
if(u==0) {
assert(now==0);
break;
}
if(--now<=len[link[u]]) u=link[u];
}
}
Len[i]=now;
}
}
ll sol() {
ll an=0;
for(int i=1;i<=all;++i) {
an+=max(0,len[i]-max(Len[endpos[i]],len[link[i]]));
}
return an;
}
} sam0,sam1;
int main() {
freopen("name.in","r",stdin);
freopen("name.out","w",stdout);
scanf("%s",S),n=strlen(S);
sam0.init();
for(int i=0;i<n;++i) sam0.extend(S[i]-'a',i);
sam0.build();
read(Q);
while(Q--) {
scanf("%s",T),m=strlen(T);
int l,r; read(l),read(r);
sam1.init();
for(int i=0;i<m;++i) sam1.extend(T[i]-'a',i);
sam0.travel(T,m,l,r);
printf("%lld
",sam1.sol());
}
return 0;
}