您的名字。
做法好像和其他题解不太一样。
空间少个 \(\log\),而且常数小。
description
给定长度为 \(n\) 的字符串 \(S\)。
\(Q\) 次询问,第 \(t\) 次给定字符串 \(T_t\) 以及正整数 \(l,r\in [1,n]\)。
每次询问回答 \(T_t\) 有几个本质不同子串在 \(S_{l\dots r}\) 中没有出现过。
不强制在线
-
\(n\leq 5\times 10^5\)
-
\(Q\leq 10^5\)
-
\(|T_t|\leq 5\times 10^5,\sum |T_t|\leq 10^6\)
solution
按照 \(r\) 不降离线。
对于第 \(t\) 个询问,先计算出来 \(w_t\) 表示字符串 \(T_t\) 的本质不同子串个数。
然后要容斥掉在 \(S_{l\dots r}\) 中出现过的。
通过在 SAM 上跳 parent 匹配出 \(T_t\) 的每个前缀 \(i\) 的后缀和 \(S\) 的最长匹配长度 \(Len_i\),并求出匹配到了 \(S\) 的 SAM 上的点 \(p_i\)。
再计算出 \(T_t\) 的每个前缀 \(i\) 有哪些长度的后缀是在 \(T_t\) 中第一次出现,容易发现,这些后缀的长度是连续的,构成区间 \([\text{minl}_i,\text{maxl}_i]\),这一步可以在 \(T_t\) 的 SAM 的 parent 树上 dfs 实现。
然后我们需要知道前缀 \(i\) 和 \(S_{l\dots r}\) 的最长匹配长度,和 \([\text{minl}_i,\text{maxl}_i]\) 取交就是对答案的贡献。
我们已经求出了前缀 \(i\) 匹配到的点 \(p\),那么能只有 \(p\) 和其 parent 树上的祖先可以和前缀 \(i\) 匹配。因为我们离线了,所以不需要考虑右端点的限制。设 \(S\) 的 SAM 上点 \(x\) 当前的 \(\text{endpos}\) 集合中最大的元素是 \(\text{maxr}(x)\),\(x\) 表示的字符串中最长的长度记作 \(\text{len}(x)\)。那么 \(x\) 代表的一些字符串和前缀 \(i\) 的最长匹配长度就是 \(\min(\text{maxr}(x)-l+1,\text{len}(x))\)。
容易发现,点 \(p\) 向上走的过程中,\(\text{len}\) 是递减,\(\text{maxr}\) 是不降的,所以可以二分 \(\text{maxr}-\text{len}+1\) 和 \(l\) 的大小,最长长度一定在临界处两个点取到。
具体实现可以用 LCT,每次离线的右指针右移就 access,然后在 Splay 上打标记,二分也可以在 Splay 上完成。注意,这时求出来的最长长度可能包含了 \(p\) 点原本不能匹配的字符串(即 \(Len_i<\text{len}(p)\),但是实际有可能取到比 \(Len_i\) 大),所以求出来的长度要和 \(Len_i\) 取 \(\min\)。
时间复杂度 \(O(n\log n+\sum |T|\log |T|)\),空间复杂度 \(O((n+\sum |T|)|\Sigma|)\)。
顺便提一下,这题原数据似乎有一部分比较水,Splay 上二分的方向写反得了 54 分 qwq
code
#include<bits/stdc++.h>
using namespace std;
using E=long long;
constexpr int inf=0x3f3f3f3f;
struct SAM{
vector<array<int,26>> ch;
vector<int> fa,len;
int idx,lst;
int extend(int c){
idx++;
len[idx]=len[lst]+1;
int p=lst;
while(p&&ch[p][c]==0){
ch[p][c]=idx;
p=fa[p];
}
if(!p) return fa[idx]=1,lst=idx,idx;
int q=ch[p][c];
if(len[q]==len[p]+1) return fa[idx]=q,lst=idx,idx;
int np=idx;
idx++;
fa[idx]=fa[q],ch[idx]=ch[q],len[idx]=len[p]+1;
while(p&&ch[p][c]==q){
ch[p][c]=idx;
p=fa[p];
}
return fa[q]=fa[np]=idx,lst=np,np;
}
SAM(int n){
ch.resize(n*2+1);
fa.resize(n*2+1),len.resize(n*2+1);
lst=idx=1;
}
};
struct linkCutTree{
struct node{
int son[2],par,tag,w,val;
node(){
son[0]=son[1]=par=tag=w=val=0;
}
};
vector<node> tr;
linkCutTree(int sz){
tr=vector<node>(sz+1);
}
#define ls(u) tr[(u)].son[0]
#define rs(u) tr[(u)].son[1]
#define par(u) tr[(u)].par
#define w(u) tr[(u)].w
#define tag(u) tr[(u)].tag
#define val(u) tr[(u)].val
void spread(int u,int v){
if(!u) return;
w(u)=v-val(u)+1;
tag(u)=v;
}
void dn(int u){
if(tag(u)) spread(ls(u),tag(u)),spread(rs(u),tag(u)),tag(u)=0;
}
int get(int u){ return u==rs(par(u)); }
int nroot(int u){ return u==ls(par(u))||get(u); }
void rotate(int u){
int p=par(u),j=par(p),op=get(u);
if(nroot(p)) tr[j].son[get(p)]=u;
if(tr[u].son[op^1]) par(tr[u].son[op^1])=p;
tr[p].son[op]=tr[u].son[op^1];
tr[u].son[op^1]=p;
par(p)=u;
par(u)=j;
}
void update(int u){
if(nroot(u)) update(par(u));
dn(u);
}
void splay(int u){
update(u);
while(nroot(u)){
int f=par(u);
if(nroot(f)){
if(get(u)==get(f)) rotate(f);
else rotate(u);
}
rotate(u);
}
}
void access(int u){
int p=0;
while(u){
splay(u);
rs(u)=p;
p=u;
u=par(u);
}
}
void reset(int u,int v,int fa){
par(u)=fa,val(u)=v; w(u)=-v+1;
}
#undef ls
#undef rs
#undef tag
#undef par
#undef w
#undef val
};
int main(){
#ifdef zzafanti
freopen("in.in","r",stdin);
#endif // zzafanti
cin.tie(nullptr),cout.tie(nullptr)->sync_with_stdio(false);
string Str;
cin>>Str;
int n=Str.size();
Str=" "+Str;
SAM sam1(n);
vector<int> pos(n+1);
int Q;
cin>>Q;
vector<pair<int,int>> querys(Q+1);
vector<E> ans(Q+1);
vector<vector<pair<int,string>>> Tp(n+1);
for(int t=1; t<=Q; t++){
string T;
cin>>T;
int l,r;
cin>>l>>r;
Tp[r].emplace_back(make_pair(t,T));
querys[t]=make_pair(l,r);
}
for(int i=1; i<=n; i++){
pos[i]=sam1.extend(Str[i]-'a');
}
linkCutTree S(sam1.idx);
for(int i=1; i<=sam1.idx; i++){
S.reset(i,sam1.len[i],sam1.fa[i]);
}
for(int R=1; R<=n; R++){
S.access(pos[R]);
S.splay(1);
S.spread(1,R);
for(auto pt:Tp[R]){
int l=querys[pt.first].first,r=querys[pt.first].second;
string &T=pt.second;
int t=pt.first;
int m=T.size();
SAM sam(m+1);
vector<int> maxr(m*2+1);
for(int i=1; i<=m; i++){
maxr[sam.extend(T[i-1]-'a')]=i;
}
vector<vector<int>> ver(m*2+1);
for(int i=2; i<=sam.idx; i++){
ans[t]+=(sam.len[i]-sam.len[sam.fa[i]]);
ver[sam.fa[i]].emplace_back(i);
}
vector<int> maxl(m+1,0),minl(m+1,inf);
function<void(int)> dfs=[&](int u)->void{
for(auto p:ver[u]){
dfs(p);
maxr[u]=max(maxr[u],maxr[p]);
}
maxl[maxr[u]]=max(maxl[maxr[u]],sam.len[u]);
minl[maxr[u]]=min(minl[maxr[u]],sam.len[sam.fa[u]]+1);
assert(minl[maxr[u]]>0);
};
dfs(1);
int now=0,p=1;
for(int i=1; i<=m; i++){
int c=T[i-1]-'a';
while(p!=1&&sam1.ch[p][c]==0) p=sam1.fa[p],now=sam1.len[p];
if(sam1.ch[p][c]) p=sam1.ch[p][c],now++;
int res=0;
S.access(p);
S.splay(p);
//cerr<<S.tr[p].val<<endl;
int tmp=p,lst=0,llst=0;
while(tmp){
llst=tmp;
//cerr<<tmp<<' '<<S.tr[tmp].w<<' '<<l<<endl;
if(S.tr[tmp].w<=l) lst=tmp,tmp=S.tr[tmp].son[0];
else tmp=S.tr[tmp].son[1];
if(tmp) S.dn(tmp);
}
tmp=lst;
if(tmp){
S.splay(tmp);
//cerr<<S.tr[tmp].w<<' '<<S.tr[tmp].val<<endl;
res=(S.tr[tmp].w+S.tr[tmp].val-1)-l+1;
res=max(0,res);
tmp=S.tr[tmp].son[0];
}else res=S.tr[p].val,S.splay(llst);
if(tmp){
while(S.tr[tmp].son[1]) tmp=S.tr[tmp].son[1],S.dn(tmp);
S.splay(tmp);
//cerr<<tmp<<' '<<S.tr[tmp].val<<' '<<S.tr[tmp].w<<endl;
res=max(res,S.tr[tmp].val);
}
res=min(res,now);
ans[t]-=max(0,min(maxl[i],res)-minl[i]+1);
}
}
}
for(int i=1; i<=Q; i++) cout<<ans[i]<<'\n';
return 0;
}
标签:par,idx,int,text,tr,len,名字,NOI2018
From: https://www.cnblogs.com/FreshOrange/p/17981771