【题解】 区间本质不同子串个数 SAM+LCT+线段树 luogu6292
Legend
同标题。
Link \(\textrm{to Luogu}\)。
Editorial
考虑离线。询问右端点从左到右排序。
我们把每一个子串最后一次出现的位置的左端点设置成 \(+1\)。这样查询区间和就是答案。
显然,经过 SAM 中一个节点 \(x\) 时,会更新 \(x\) 沿 fail 树到根这条路径上的子串的最后一次出现的位置。
这个更新操作就很像 LCT 的 access 操作。我们不妨就用 LCT 维护子串的最后一次出现的位置。
对于最后一次出现的位置相同的结点,它们在 fail 树上是一条链。
所以长度是连续的,可以线段树区间修改最后一次出现的位置的左端点的权值。
现在唯一的问题就是,这样做的时间复杂度是什么?
一个结论是 LCT 的 access 内层循环次数是 \(O(n \log n)\) 的,故复杂度正确。
时间复杂度 \(O(q \log n + n\log^2 n)\)。
Editorial
#include <bits/stdc++.h>
#define debug(...) fprintf(stderr ,__VA_ARGS__)
#define __FILE(x)\
freopen(#x".in" ,"r" ,stdin);\
freopen(#x".out" ,"w" ,stdout)
#define LL long long
const int MX = 2e5 + 23;
const LL MOD = 998244353;
int read(){
char k = getchar(); int x = 0;
while(k < '0' || k > '9') k = getchar();
while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
return x;
}
char str[MX]; int q;
struct SEGMENTTREE{
struct node{
int l ,r;
LL sum ,add;
node *lch ,*rch;
}*root;
void pushup(node *x){x->sum = x->lch->sum + x->rch->sum;}
void doadd(node *x ,LL v){x->add += v ,x->sum += (x->r - x->l + 1) * v;}
void pushdown(node *x){
if(x->add){
doadd(x->lch ,x->add);
doadd(x->rch ,x->add);
x->add = 0;
}
}
node *build(int l ,int r){
node *x = new node;
x->l = l ,x->r = r;
x->sum = x->add = 0LL;
if(l == r) x->lch = x->rch = nullptr;
else{
int mid = (l + r) >> 1;
x->lch = build(l ,mid);
x->rch = build(mid + 1 ,r);
pushup(x);
}return x;
}
void buildtree(int l ,int r){root = build(l ,r);}
void add(node *x ,int l ,int r ,LL v){
if(l <= x->l && x->r <= r) return doadd(x ,v);
pushdown(x);
if(l <= x->lch->r) add(x->lch ,l ,r ,v);
if(r > x->lch->r) add(x->rch ,l ,r ,v);
return pushup(x);
}
void add(int l ,int r ,LL v){add(root ,l ,r ,v);}
LL sum(node *x ,int l ,int r){
if(x->r < l || x->l > r) return 0LL;
if(l <= x->l && x->r <= r) return x->sum;
pushdown(x);
return sum(x->lch ,l ,r) + sum(x->rch ,l ,r);
}
LL sum(int l ,int r){return sum(root ,l ,r);}
}S;
struct LCT{
#define lch(x) ch[x][0]
#define rch(x) ch[x][1]
LCT(){cov[0] = 0;}
int ch[MX][2] ,fa[MX] ,cov[MX] ,v[MX];
int get(int x){return x == rch(fa[x]);}
int Nroot(int x){return get(x) || x == lch(fa[x]);}
void docov(int x ,int V){cov[x] = v[x] = V;}
void pushdown(int x){
if(cov[x] == 0) return;
if(lch(x)) docov(lch(x) ,cov[x]);
if(rch(x)) docov(rch(x) ,cov[x]);
cov[x] = 0;
}
void rotate(int x){
int f = fa[x] ,gf = fa[f] ,which = get(x) ,W = ch[x][!which];
if(Nroot(f)) ch[gf][get(f)] = x;
ch[x][!which] = f ,ch[f][which] = W;
if(W) fa[W] = f;
fa[f] = x ,fa[x] = gf;
}
int stk[MX] ,dep;
void splay(int x){
int f = x; stk[++dep] = f;
while(Nroot(f)) stk[++dep] = f = fa[f];
while(dep) pushdown(stk[dep--]);
while(Nroot(x)){
if(Nroot(f)) rotate(get(x) == get(f) ? f : x);
rotate(x);
};
}
void access(int x ,int id);
void link(int x ,int f){fa[x] = f;}
#undef lch
#undef rch
}lct;
struct SAM{
int tot ,las;
SAM(){tot = las = 1;}
struct NODE{int ch[26] ,len ,link;}a[MX];
void extend(int c){
int p = las ,cur = las = ++tot;
a[cur].len = a[p].len + 1;
for( ; p && !a[p].ch[c] ; p = a[p].link) a[p].ch[c] = cur;
if(!p) return a[cur].link = 1 ,void();
int q = a[p].ch[c];
if(a[p].len + 1 == a[q].len) return a[cur].link = q ,void();
int cl = ++tot;
a[cl] = a[q];
a[cl].len = a[p].len + 1;
a[q].link = a[cur].link = cl;
for( ; p && a[p].ch[c] == q ; p = a[p].link) a[p].ch[c] = cl;
}
void build(){ for(int i = 2 ; i <= tot ; ++i) lct.link(i ,a[i].link);}
}A;
int cnt;
void LCT::access(int x ,int id){
if(id == 6){
debug("SADFASDFADSF\n");
}
int rt = x ,y = 0;
for( ; x ; x = fa[y = x]){
++cnt;
splay(x) ,ch[x][1] = y;
if(v[x] == 0 || x == 1) continue;
int k = fa[x];
S.add(v[x] - A.a[x].len + 1 ,v[x] - A.a[k].len ,-1);
//debug("del [%d ,%d]\n" ,v[x] - A.a[x].len + 1 ,v[x] - A.a[A.a[k].link].len);
}
docov(y ,id);
// debug("add [%d, %d]\n" ,id - A.a[rt].len + 1 ,id);
S.add(id - A.a[rt].len + 1 ,id ,1);
}
struct QUERY{
int l ,r ,id;
}Q[MX];
bool cmp(QUERY A ,QUERY B){return A.r < B.r;}
LL output[MX];
int main(){
__FILE(区间本质不同子串个数);
scanf("%s" ,str + 1);
int n = strlen(str + 1);
for(int i = 1 ; i <= n ; ++i) A.extend(str[i] - 'a');
A.build();
S.buildtree(0 ,n);
q = read();
for(int i = 1 ,l ,r ; i <= q ; ++i){
l = read() ,r = read();
Q[i] = (QUERY){l ,r ,i};
}
std::sort(Q + 1 ,Q + 1 + q ,cmp);
int R = 1 ,x = 1;
for(int i = 1 ,l ,r ; i <= q ; ++i){
l = Q[i].l ,r = Q[i].r;
while(R <= r){
x = A.a[x].ch[str[R] - 'a'];
lct.access(x ,R);
++R;
}
output[Q[i].id] = S.sum(l ,r);
debug("Query %d %d\n" ,l ,r);
}
for(int i = 1 ; i <= q ; ++i) printf("%lld\n" ,output[i]);
debug("%d\n" ,cnt);
return 0;
}