【题解】 区间本质不同子串个数 SAM+LCT+线段树 luogu6292

Legend

同标题。

Link ( extrm{to Luogu})

Editorial

考虑离线。询问右端点从左到右排序。

我们把每一个子串最后一次出现的位置的左端点设置成 (+1)。这样查询区间和就是答案。

显然,经过 SAM 中一个节点 (x) 时,会更新 (x) 沿 fail 树到根这条路径上的子串的最后一次出现的位置

这个更新操作就很像 LCT 的 access 操作。我们不妨就用 LCT 维护子串的最后一次出现的位置

对于最后一次出现的位置相同的结点,它们在 fail 树上是一条链。

所以长度是连续的,可以线段树区间修改最后一次出现的位置的左端点的权值。

现在唯一的问题就是,这样做的时间复杂度是什么?

一个结论是 LCT 的 access 内层循环次数是 (O(n log n)) 的,故复杂度正确。

时间复杂度 (O(q log n + nlog^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
");
	}
	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]
" ,v[x] - A.a[x].len + 1 ,v[x] - A.a[A.a[k].link].len);
	}
	docov(y ,id);
	// debug("add [%d, %d]
" ,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
" ,l ,r);
	}
	for(int i = 1 ; i <= q ; ++i) printf("%lld
" ,output[i]);
	debug("%d
" ,cnt);
	return 0;
}
原文地址:https://www.cnblogs.com/imakf/p/14259980.html