loj 2720 [NOI2018] 你的名字
loj 2720 [NOI2018] 你的名字
有一个串 \(S\) , \(Q\) 次询问,每次给出一个串 \(T\) ,和 \(l,r\) .
求 \(T\) 中有多少非空本质不同子串没有在 \(S[l \cdots r]\) 中出现过
\(|S| \le 5 \times 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\n",sam1.sol());
}
return 0;
}