sam和lct维护一类字符串问题

洛谷P6292 区间本质不同子串个数

解题思路

简单卸卸,将询问离线下来,像HH的项链一样对于每一个子串记录其出现的最右位置,通过后缀自动机可以将过多的子串压起来,但暴力修改颜色肯定不行,用 lct 像树点涂色一样维护即可

代码

#include <queue>
#include <vector>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define MP make_pair
#define ll long long
#define fi first
#define se second
using namespace std;

template <typename T>
void read(T &x) {
    x = 0; bool f = 0;
    char c = getchar();
    for (;!isdigit(c);c=getchar()) if (c=='-') f=1;
    for (;isdigit(c);c=getchar()) x=x*10+(c^48);
    if (f) x=-x;
}

template<typename F>
inline void write(F x)
{
	static short st[30];short tp=0;
	if(x<0) putchar('-'),x=-x;
	do st[++tp]=x%10,x/=10; while(x);
	while(tp) putchar('0'|st[tp--]);
	putchar('
');
}

template <typename T>
inline void Mx(T &x, T y) { x < y && (x = y); }

template <typename T>
inline void Mn(T &x, T y) { x > y && (x = y); }

const int N = 600500;
int fa[N], len[N], pos[N];
int ch[N][26], cnt = 1, las = 1;
int add(int c) {
	int p = las, np = las = ++cnt;
	len[np] = len[p] + 1;
	for (; p && !ch[p][c]; p = fa[p]) ch[p][c] = np;
	if (!p) return fa[np] = 1, np;
	int q = ch[p][c];
	if (len[q] == len[p] + 1) return fa[np] = q, np;
	int nq = ++cnt; fa[nq] = fa[q];
	memcpy(ch[nq], ch[q], sizeof(ch[q]));
	fa[q] = fa[np] = nq, len[nq] = len[p] + 1;
	for (; ch[p][c] == q; p = fa[p]) ch[p][c] = nq;
	return np;
}

ll d1[N], d2[N], m, n;
void add(int x, ll v1, ll v2) {
	for (; x <= n; x += x & -x) 
		d1[x] += v1, d2[x] += v2;
}

ll query(int x) {
	ll r1 = 0, r2 = 0;
	for (int t = x; t; t -= t & -t)
		r1 += d1[t], r2 += d2[t];
	return r1 * (x + 1) - r2;
}

void add(int l, int r, ll d) { add(l, d, d * l), add(r + 1, -d, -d * (r + 1)); }
ll query(int l, int r) { return query(r) - query(l - 1); }

struct LCT {
	#define ls son[x][0]
	#define rs son[x][1]
	int son[N][2], f[N], tag[N];
	bool nroot(int x) {
		return son[f[x]][0] == x || son[f[x]][1] == x; 
	}
	void rotate(int x) {
		int y = f[x], z = f[y];
		int k = son[y][1] == x, w = son[x][!k];
		if (nroot(y)) son[z][son[z][1]==y] = x; f[x] = z;
		if (w) f[w] = y; son[y][k] = w;
		f[y] = x, son[x][!k] = y;
	}
	
	void spread(int x) {
		if (!tag[x]) return;
		tag[ls] = tag[rs] = tag[x];
//		tag[x] = 0;
	}
	
	int st[N];
	void splay(int x) {
		int y = x, z = 0; st[++z] = y;
		while (nroot(y)) st[++z] = y = f[y];
		while (z) spread(st[z--]); 
		while (nroot(x)) {
			y = f[x], z = f[y];
			if (nroot(y)) {
				if ((son[y][0] == x) ^ (son[z][0] == y)) 
					rotate(x);
				else rotate(y);
			}
			rotate(x);
		}
	}
	void access(int x, int tg) {
		int y = 0;
		for (y = 0; x; x = f[y = x]) {
			splay(x); rs = y;
			if (tag[x]) {
				add(tag[x] - len[x] + 1, tag[x] - len[f[x]], -1), tag[x] = tg;
//				write(x);
			}
		}
		splay(y), tag[y] = tg;
//		write(y);
		add(1, tg, 1);
	}
}Lct;

ll ans[N];
vector<pair<int, int> > v[N];
char s[N];
int main() {
	scanf ("%s", s + 1), n = strlen(s + 1);
	for (int i = 1;i <= n; i++) pos[i] = add(s[i]-'a');
	for (int i = 2;i <= cnt; i++) Lct.f[i] = fa[i];
//	for (int i = 1;i <= n; i++) write(pos[i]);
	read(m);
	for (int i = 1, l, r;i <= m; i++) 
		read(l), read(r), v[r].push_back(MP(i, l));
	for (int i = 1;i <= n; i++) {
		Lct.access(pos[i], i);
		for (auto t: v[i])
			ans[t.fi] = query(t.se, i);
	}
//	puts("");
	for (int i = 1;i <= m; i++) write(ans[i]);
	return 0;
}

LOJ6041. 「雅礼集训 2017 Day7」事情的相似度

解题思路

题面可以改成“区间最长出现过两个及以上的子串”,见到了也要会做

和上题一样,好像字符串和区间有关的蛤

维护一下每个集合的颜色,然后用线段树更新答案即可

原文地址:https://www.cnblogs.com/Hs-black/p/13127036.html