Loading

区间本质不同子串个数

https://www.luogu.com.cn/problem/P6292

题解

一道LCT+SAM的板子题。

考虑一个简化版的问题:区间数颜色。

显然可以离线后枚举右端点,然后求出左端点在 \([1,r]\) 这个范围内,不同颜色的个数。我们考虑新加进来一个 \(c\),先对每个 \(l\) 都增加 \(1\),记 \(c\) 上一次出现的位置为 \(lst_c\),如果没有出现则为 \(0\)。那么在 \([1,lst_c]\) 范围内的左端点都被算重,需要减少 \(1\)。使用数据结构维护即可。

这道题是类似的。

考虑增加了一个点,那么他会增加很多字符串。具体来说是其parent tree上的祖先的集合内的串。我们还是考虑维护一个 \(lst\) 代表每个串上一次出现的位置(这里串出现位置指右端点出现位置)。显然对于一个endpos等价类其 \(lst\) 永远相同,所以对于sam上每个状态维护 \(lst\) 即可。那么对于一次操作可以看成从根到该点全部赋值。然后考虑减去算重的部分,对于每个状态,在 \([lst-len+1,lst-len_{fa}]\) 这个范围内的已经算过了,所以要减去 \(1\)。还是同样的维护方法。

这样直接暴力跳父亲的复杂度会被卡成 \(O(n^2\log{n})\)。我们发现这个过程非常像LCT的Access操作,而对于 \(lst\) 相同的一条链其实可以看做一个点来处理。所以模拟access的即可。复杂度均摊 \(O(n\log^2{n}+m\log{n})\)

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=500005;
template <typename T>
void read(T &x) {
	T flag=1;
	char cha=getchar();
	for (; '0'>cha||cha>'9'; cha=getchar()) if (cha=='-') flag=-1;
	for (x=0; '0'<=cha&&cha<='9'; cha=getchar()) x=x*10+cha-'0';
	x*=flag;
}
template <typename T>
void cmax(T &x, T y) { x=max(x, y); }
template <typename T>
void cmin(T &x, T y) { x=min(x, y); }
void file() {
#ifdef LOCAL
	freopen("test.in", "r", stdin);
#endif
}
int n, m;
char s[MAXN];
int son[MAXN][26], len[MAXN], fail[MAXN], lst=1, tot=1;
void ins(int c) {
	int p=lst;
	int np=lst=++tot;
	len[np]=len[p]+1;
	for (; p&&!son[p][c]; p=fail[p]) son[p][c]=np;
	if (!p) fail[np]=1;
	else {
		int q=son[p][c];
		if (len[q]==len[p]+1) fail[np]=q;
		else {
			int nq=++tot;
			for (int i=0; i<26; i++) son[nq][i]=son[q][i];
			fail[nq]=fail[q];
			len[nq]=len[p]+1;
			fail[q]=fail[np]=nq;
			for (; p&&son[p][c]==q; p=fail[p]) son[p][c]=nq;
		}
	}
}
ll sum[MAXN<<2], add[MAXN<<2];
void push_add(int p, int l, int r, int v) {
	sum[p]+=1ll*(r-l+1)*v;
	add[p]+=v;
}
void push_down(int p, int l, int r) {
	if (add[p]) {
		int mid=(l+r)>>1;
		push_add(p<<1, l, mid, add[p]);
		push_add(p<<1|1, mid+1, r, add[p]);
		add[p]=0;
	}
}
void push_up(int p) {
	sum[p]=sum[p<<1]+sum[p<<1|1];
}
void change(int p, int l, int r, int x, int y, int v) {
	if (l>y||r<x) return;
	if (x<=l&&r<=y) {
		push_add(p, l, r, v);
		return;
	}
	push_down(p, l, r);
	int mid=(l+r)>>1;
	change(p<<1, l, mid, x, y, v);
	change(p<<1|1, mid+1, r, x, y, v);
	push_up(p);
}
ll ask(int p, int l, int r, int x, int y) {
	if (l>y||r<x) return 0;
	if (x<=l&&r<=y) return sum[p];
	push_down(p, l, r);
	int mid=(l+r)>>1;
	return ask(p<<1, l, mid, x, y)+ask(p<<1|1, mid+1, r, x, y);
}
int fa[MAXN], ch[MAXN][2], val[MAXN], tag[MAXN];
void pushtag(int p, int v) {
	val[p]=tag[p]=v;
}
void pushdown(int p) {
	if (tag[p]) {
		if (ch[p][0]) pushtag(ch[p][0], tag[p]);
		if (ch[p][1]) pushtag(ch[p][1], tag[p]);
		tag[p]=0;
	}
}
int get(int x) {
	return ch[fa[x]][1]==x;
}
bool root(int x) {
	return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;
}
void update(int x) {
	if (!root(x)) update(fa[x]);
	pushdown(x);
}
void rotate(int x) {
	int y=fa[x], z=fa[y], w=get(x);
	if (!root(y)) ch[z][ch[z][1]==y]=x;
	fa[x]=z;
	ch[y][w]=ch[x][w^1]; fa[ch[x][w^1]]=y;
	ch[x][w^1]=y; fa[y]=x;
}
void splay(int x) {
	update(x);
	for (int y=fa[x]; !root(x); rotate(x), y=fa[x]) if (!root(y)) rotate(get(x)==get(y)?y:x);
}
void access(int x, int v) {
	int y;
	change(1, 1, n, 1, v, 1);
	for (y=0; x; y=x, x=fa[x]) {
		splay(x); ch[x][1]=y;
		if (val[x]) change(1, 1, n, val[x]-len[x]+1, val[x]-len[fa[x]], -1);
	}
	pushtag(y, v);
}
struct query{
	int l, r, id;
	friend bool operator < (query a, query b) {
		if (a.r==b.r) return a.l<b.l;
		return a.r<b.r;
	}
}q[MAXN];
ll ans[MAXN];
int pos[MAXN];
int main() {
	scanf("%s", s+1);
	n=strlen(s+1);
	for (int i=1; i<=n; i++) ins(s[i]-'a'), pos[i]=lst;
	for (int i=2; i<=tot; i++) fa[i]=fail[i];
	read(m);
	for (int i=1; i<=m; i++) {
		read(q[i].l); read(q[i].r); q[i].id=i;
	}
	sort(q+1, q+1+m);
	int now=0;
	for (int i=1; i<=m; i++) {
		while (now<q[i].r) {
			now++;
			access(pos[now], now);
		}
		ans[q[i].id]=ask(1, 1, n, q[i].l, q[i].r);
	}
	for (int i=1; i<=m; i++) printf("%lld\n", ans[i]);
	return 0;
}
posted @ 2021-03-26 23:15  Gemini7X  阅读(100)  评论(0编辑  收藏  举报
Title