字符串做题笔记

字符串做题笔记

会持续跟新吗(恼……

CF710F - String Set Queries

注意到所有串的长度只有 \(\sqrt{n}\) 种,所以直接把所有串的哈希插入 map 里,然后直接按照有效串长查询即可。

时间复杂度 \(O(n \sqrt{n} \log n)\)

ケロシの代码
const int N = 3e5 + 5;
const int V = 3e5;
const int P1 = 998244353;
const int P2 = 1e9 + 7;
int add(int x, int y, int p) { return (x + y < p ? x + y : x + y - p); }
void Add(int & x, int y, int p) { x = (x + y < p ? x + y : x + y - p); }
int sub(int x, int y, int p) { return (x < y ? x - y + p : x - y); }
void Sub(int & x, int y, int p) { x = (x < y ? x - y + p : x - y); }
int mul(int x, int y, int p) { return (1ll * x * y) % p; }
void Mul(int & x, int y, int p) { x = (1ll * x * y) % p; }
mt19937 rnd(time(0));
bool ip(int x) {
	for(int i = 2; i * i <= x; i ++) 
		if(x % i == 0) return 0;
	return 1;
}
struct Hash {
	int x, y;
	bool operator == (const Hash & A) const {
		return x == A.x && y == A.y;
	}
	bool operator < (const Hash & A) const {
		if(x == A.x) return y < A.y;
		return x < A.x;
	}
	Hash operator + (const int & A) {
		return {add(x, A, P1), add(y, A, P2)};
	}
	Hash operator - (const int & A) {
		return {sub(x, A, P1), sub(y, A, P2)};
	}
	Hash operator * (const int & A) {
		return {mul(x, A, P1), mul(y, A, P2)};
	}
	Hash & operator += (const int & A) {
		*this = * this + A;
		return *this;
	}
	Hash & operator -= (const int & A) {
		*this = * this - A;
		return *this;
	}
	Hash & operator *= (const int & A) {
		*this = * this * A;
		return *this;
	}
	Hash operator + (const Hash & A) {
		return {add(x, A.x, P1), add(y, A.y, P2)};
	}
	Hash operator - (const Hash & A) {
		return {sub(x, A.x, P1), sub(y, A.y, P2)};
	}
	Hash operator * (const Hash & A) {
		return {mul(x, A.x, P1), mul(y, A.y, P2)};
	}
	Hash & operator += (const Hash & A) {
		*this = * this + A;
		return *this;
	}
	Hash & operator -= (const Hash & A) {
		*this = * this - A;
		return *this;
	}
	Hash & operator *= (const Hash & A) {
		*this = * this * A;
		return *this;
	}
};
int q, B;
int c[N];
Hash pw[N], h[N];
map<Hash, int> mp;
void solve() {
	B = rnd() % int(1e6) + 1145;
	while(! ip(B)) B ++;
	cin >> q;
	pw[0] = {1, 1};
	FOR(i, 1, V) pw[i] = pw[i - 1] * B;
	REP(_, q) {
		int o; string s;
		cin >> o >> s;
		int n = SZ(s); s = ' ' + s;
		FOR(i, 1, n) h[i] = h[i - 1] + pw[i] * s[i];
		if(o == 1) {
			c[n] ++;
			mp[h[n] * pw[V - 1]] ++;
		}
		if(o == 2) {
			c[n] --;
			mp[h[n] * pw[V - 1]] --;
		}
		if(o == 3) {
			ll ans = 0;
			FOR(len, 1, n) if(c[len]) FOR(l, 1, n - len + 1) {
				int r = l + len - 1;
				ans += mp[(h[r] - h[l - 1]) * pw[V - l]];
			}
			cout << ans << endl << flush;
		}
	}
}

CF1780G - Delicious Dessert

建出 SAM,通过在 link tree 上 dp 算出每个等价类的出现次数 \(f_u\),若等价类中字串长度为 \([l,r]\),那么贡献为 \([l,r]\) 中为 \(f_u\) 约数的个数。

不难想到预处理每个数的约数,然后查询的时候直接二分找 \([l,r]\) 中有多少约数即可。

时间复杂度 \(O(n \log n)\)

ケロシの代码
const int N = 2e6 + 5;
int n;
string s;
struct Node {
	int len, link;
	int nxt[26];
} st[N];
int lst, sz;
vector<int> e[N], d[N >> 1];
int f[N];
ll ans;
void init() {
	st[1].link = - 1;
	st[1].len = 0;
	lst = sz = 1;
}
void extend(int c) {
	int cur = ++ sz;
	st[cur].len = st[lst].len + 1;
	f[cur] = 1;
	int p = lst;
	while(p != - 1 && ! st[p].nxt[c]) {
		st[p].nxt[c] = cur;
		p = st[p].link;
	}
	if(p == - 1) {
		st[cur].link = 1;
	}
	else {
		int q = st[p].nxt[c];
		if(st[p].len + 1 == st[q].len) {
			st[cur].link = q;
		}
		else {
			int cln = ++ sz;
			st[cln].len = st[p].len + 1;
			REP(i, 26) st[cln].nxt[i] = st[q].nxt[i];
			st[cln].link = st[q].link;
			while(p != - 1 && st[p].nxt[c] == q) {
				st[p].nxt[c] = cln;
				p = st[p].link;
			}
			st[cur].link = st[q].link = cln;
		}
	}
	lst = cur;
}
int F(int r, int x) {
	return lower_bound(ALL(d[x]), r + 1) - BG(d[x]);
}
int F(int l, int r, int x) {
	return F(r, x) - F(l - 1, x);
}
void dfs(int u) {
	for(int v : e[u]) {
		dfs(v);
		f[u] += f[v];
	}
	if(u > 1) ans += 1ll * F(st[st[u].link].len + 1, st[u].len, f[u]) * f[u];
}
void solve() {
	cin >> n;
	cin >> s; s = ' ' + s;
	init();
	FOR(i, 1, n) extend(s[i] - 'a');
	FOR(i, 2, sz) e[st[i].link].push_back(i);
	FOR(i, 1, n) for(int j = i; j <= n; j += i)
		d[j].push_back(i);
	dfs(1);
	cout << ans << endl;
}

CF452E - Three strings

考虑将三个串建出一个广义 SAM,然后把每个等价类在三个串的出现次数乘起来,做一次区间加即可。

时间复杂度 \(O(n)\)

ケロシの代码
const int N = 6e5 + 5;
const int P = 1e9 + 7;
const int INF = 1e9 + 7;
inline int add(int x, int y) { return (x + y < P ? x + y : x + y - P); }
inline void Add(int & x, int y) { x = (x + y < P ? x + y : x + y - P); }
inline int sub(int x, int y) { return (x < y ? x - y + P : x - y); }
inline void Sub(int & x, int y) { x = (x < y ? x - y + P : x - y); }
inline int mul(int x, int y) { return (1ll * x * y) % P; }
inline void Mul(int & x, int y) { x = (1ll * x * y) % P; }
inline int mul(initializer_list<int> a) {
	int res = 1;
	for(int x : a) Mul(res, x);
	return res;
}
int a[3];
string s[3];
int tr[N][26], idx;
int pre[N], id[N], fa[N];
int fi[N], ne[N], to[N], ecnt;
int f[N][3], c[N];
void add_edge(int u, int v) {
	ne[++ ecnt] = fi[u];
	to[ecnt] = v;
	fi[u] = ecnt;
}
struct Node {
	int len, link;
	map<int, int> nxt;
} st[N];
int sz;
int insert(int x, int lst) {
	int cur = ++ sz;
	st[cur].len = st[lst].len + 1;
	int p = lst;
	while(p != - 1 && ! st[p].nxt.count(x)) {
		st[p].nxt[x] = cur;
		p = st[p].link;
	}
	if(p == - 1) {
		st[cur].link = 1;
	}
	else {
		int q = st[p].nxt[x];
		if(st[p].len + 1 == st[q].len) {
			st[cur].link = q;
		}
		else {
			int cln = ++ sz;
			st[cln].len = st[p].len + 1;
			st[cln].nxt = st[q].nxt;
			st[cln].link = st[q].link;
			while(p != - 1 && st[p].nxt[x] == q) {
				st[p].nxt[x] = cln;
				p = st[p].link;
			}
			st[cur].link = st[q].link = cln;
		}
	}
	return cur;
}
void build() {
	st[1].link = - 1;
	id[0] = sz = 1;
	queue<int> q;
	REP(i, 26) if(tr[0][i]) q.push(tr[0][i]);
	while(! q.empty()) {
		int u = q.front();
		q.pop();
		id[u] = insert(pre[u], id[fa[u]]);
		REP(i, 26) if(tr[u][i]) q.push(tr[u][i]);
	}
	FOR(i, 2, sz) add_edge(st[i].link, i);
}
void dfs(int u) {
	for(int i = fi[u]; i; i = ne[i]) {
		int v = to[i];
		dfs(v);
		REP(i, 3) f[u][i] += f[v][i];
	}
}
void solve() {
	int len = INF;
	REP(i, 3) {
		cin >> s[i];
		a[i] = SZ(s[i]); s[i] = ' ' + s[i];
		chmin(len, a[i]);
		int u = 0;
		FOR(j, 1, a[i]) {
			if(! tr[u][s[i][j] - 'a']) {
				tr[u][s[i][j] - 'a'] = ++ idx;
				fa[idx] = u;
				pre[idx] = s[i][j] - 'a';
			}
			u = tr[u][s[i][j] - 'a'];
		}
	}
	build();
	REP(i, 3) {
		int u = 1;
		FOR(j, 1, a[i]) {
			u = st[u].nxt[s[i][j] - 'a'];
			f[u][i] ++;
		}
	}
	dfs(1);
	FOR(u, 2, sz) {
		int val = mul({f[u][0], f[u][1], f[u][2]});
		Add(c[st[st[u].link].len + 1], val);
		Sub(c[st[u].len + 1], val);
	}
	FOR(i, 1, len) Add(c[i], c[i - 1]);
	FOR(i, 1, len) cout << c[i] << " "; cout << endl;
}

CF204E - Little Elephant and Strings

首先将所有的字符串建出广义 SAM,然后考虑 SAM 上一个节点在多少个字符串里出现过。

不难发现每个字符串在 link tree 上的贡献是每个前缀对应节点建出的一颗类似虚树的东西,虚树上每个点都增加 \(1\) 的贡献。

考虑直接差分,将所有前缀对应节点都加一,将 dfn 序相邻的两个的 LCA 减一,算子树和即可。

接下来考虑如何计算答案,考虑对于每个右端点,左端点对应子串的出现次数的单调的,所以直接在 link tree 上倍增即可。

时间复杂度 \(O(n \log n)\)

ケロシの代码
const int N = 2e5 + 5;
int n, k, a[N];
string s[N];
int tr[N][26], idx;
vector<int> e[N];
int pre[N], id[N], fa[N];
int fi[N], ne[N], to[N], ecnt;
int dfn[N], cnt, d[N], f[N][19], F[N];
void add(int u, int v) {
	ne[++ ecnt] = fi[u];
	to[ecnt] = v;
	fi[u] = ecnt;
}
struct Node {
	int len, link;
	map<int, int> nxt;
} st[N];
int sz;
int insert(int x, int lst) {
	int cur = ++ sz;
	st[cur].len = st[lst].len + 1;
	int p = lst;
	while(p != - 1 && ! st[p].nxt.count(x)) {
		st[p].nxt[x] = cur;
		p = st[p].link;
	}
	if(p == - 1) {
		st[cur].link = 1;
	}
	else {
		int q = st[p].nxt[x];
		if(st[p].len + 1 == st[q].len) {
			st[cur].link = q;
		}
		else {
			int cln = ++ sz;
			st[cln].len = st[p].len + 1;
			st[cln].nxt = st[q].nxt;
			st[cln].link = st[q].link;
			while(p != - 1 && st[p].nxt[x] == q) {
				st[p].nxt[x] = cln;
				p = st[p].link;
			}
			st[cur].link = st[q].link = cln;
		}
	}
	return cur;
}
void build() {
	st[1].link = - 1;
	id[0] = sz = 1;
	queue<int> q;
	REP(i, 26) if(tr[0][i]) q.push(tr[0][i]);
	while(! q.empty()) {
		int u = q.front();
		q.pop();
		id[u] = insert(pre[u], id[fa[u]]);
		REP(i, 26) if(tr[u][i]) q.push(tr[u][i]);
	}
	FOR(i, 2, sz) add(st[i].link, i);
}
void dfs0(int u) {
	dfn[u] = ++ cnt;
	FOR(i, 1, 18) f[u][i] = f[f[u][i - 1]][i - 1];
	for(int i = fi[u]; i; i = ne[i]) {
		int v = to[i];
		d[v] = d[u] + 1;
		f[v][0] = u;
		dfs0(v);
	}
}
int lca(int u, int v) {
	if(d[u] < d[v]) swap(u, v);
	ROF(i, 18, 0) if(d[f[u][i]] >= d[v])
		u = f[u][i];
	if(u == v) return u;
	ROF(i, 18, 0) if(f[u][i] != f[v][i])
		u = f[u][i], v = f[v][i];
	return f[u][0];
}
void dfs1(int u) {
	for(int i = fi[u]; i; i = ne[i]) {
		int v = to[i];
		dfs1(v);
		F[u] += F[v];
	}
}
void solve() {
	cin >> n >> k;
	FOR(i, 1, n) {
		cin >> s[i];
		a[i] = SZ(s[i]); s[i] = ' ' + s[i];
		int u = 0;
		FOR(j, 1, a[i]) {
			if(! tr[u][s[i][j] - 'a']) {
				tr[u][s[i][j] - 'a'] = ++ idx;
				fa[idx] = u;
				pre[idx] = s[i][j] - 'a';
			}
			u = tr[u][s[i][j] - 'a'];
		}
	}
	build();
	FOR(i, 1, n) {
		int u = 1;
		e[i].push_back(u);
		FOR(j, 1, a[i]) {
			u = st[u].nxt[s[i][j] - 'a'];
			e[i].push_back(u);
		}
	}
	d[1] = 1; dfs0(1);
	FOR(i, 1, n) {
		sort(ALL(e[i]), [&] (int x, int y) {
			return dfn[x] < dfn[y];
		});
		FOR(j, 0, a[i]) F[e[i][j]] ++;
		FOR(j, 1, a[i]) F[lca(e[i][j - 1], e[i][j])] --;
	}
	dfs1(1);
	F[0] = n;
	FOR(i, 1, n) {
		ll ans = 0;
		FOR(j, 1, a[i]) {
			int u = e[i][j];
			ROF(l, 18, 0) if(F[f[u][l]] < k)
				u = f[u][l];
			if(F[u] < k) u = f[u][0];
			ans += st[u].len;
		}
		cout << ans << " ";
	}
	cout << endl;
}
posted @ 2025-02-12 20:18  KevinLikesCoding  阅读(31)  评论(0)    收藏  举报