2025.5.28【33OJ NOI 模拟赛 T3】字符串(AC自动机, 字符串后缀结构)

题意

传送门

题意:
给定 \(n\) 个字符串 \(s_1,s_2 \dots ,s_n\),保证它们互不相同。你需要求出满足如下条件的三元组 \((i, j, k)\) 的数量:

  • \(1 \leq i, j, j \leq n\),且 \(i, j, k\) 互不相同。
  • \(s_i\) 是 \(s_j\) 的子串, \(s_j\) 是 \(s_k\) 的子串。
  • 不存在一个 \(j' \notin \{i, j, k\}\) 使得 \(s_i\) 是 \(s_{j'}\) 的子串且 \(s_{j'}\) 是 \(s_k\) 的子串。

\(1 \leq n \leq 5 \times 10^5\),\(1 \leq \sum|s_i| \leq 10^6\)。保证所有 \(s_i\) 仅有小写字母组成。

分析

枚举最长串 \(s_k\),那么最短串 \(s_i\) 需要是 \(s_k\) 的一个子串,我们将 子串 等价成 一个前缀的后缀。
那么枚举 \(s_k\) 的一个前缀 \([1, p]\),称前缀 \(p\) 的一个 后缀字符串 为能匹配 \([1, p]\) 的后缀的字符串 \(s_u\)。那么有结论:

  • 合法的最短串 \(s_i\) 在 \(s_k\) 中的任意出现位置上都必须是 最长后缀字符串 或 次长后缀字符串。

正确性显然。因为如果不是最长或次长,那么就至少存在两个 \(j\) 满足 \(s_i\) 是 \(s_j\) 的子串, \(s_j\) 是 \(s_k\) 的子串。

建出所有 \(s_i\) 的 \(AC\) 自动机,预处理每个前缀的最长后缀字符串和次长后缀字符串,那么对 \((i, k)\) 的枚举量为 \(O(\sum |s_i|)\),\(O(n)\) 检验是否存在 有且仅有一个 合法的 \(j\),复杂度 \(O(n \sum |s_i|)\)。

注意到如果一个字符串是某个前缀的后缀字符串,并且它不是最长或次长,那么它一定在 次长字符串对应 \(fail\) 树结点的祖先链上,可以通过在 \(dfs\) 序上差分,求子树和来判断一个最长或次长后缀字符串是否满足上述 必要条件。

对于所有满足第一个必要条件的字符串 \(s_i\),如果 有且仅有一个 \(s_j\) 满足 \(s_i\) 是 \(s_j\) 的子串, \(s_j\) 是 \(s_k\) 的子串,那么 \((i, k)\) 就可以对答案贡献 \(1\)。问题是怎么快速判断一个 \(s_i\) 是否存在一个合法的 \(j\)。

只保留满足第一个条件的 \(s_i\),那么有结论:

  • 对于一个 \(s_i\),只需要从 保留的所有字符串中 考虑是否 有且仅有一个 合法的 \(j\)。

这是因为如果一个字符串 \(s_{t}\) 是 \(s_k\) 的子串并且没有被保留下来,那么保留下来的字符串中一定至少有两个包含它。如果一个 \(s_i\) 是 \(s_t\) 的子串,那么它也一定是那两个包含 \(s_t\) 的串的子串,这样就已经不合法了。

但是这样还是不太好直接判断,因为你不可能对每个 \(i\) 都枚举保留下来的 \(j\)。

将 \(s_i\) 所有出现的区间取出来,那么可以注意到若 \(s_i\) 是某个 \(s_j\) 的子串,那么 \(s_j\) 的所有出现区间里面都一定会至少包含一个 \(s_i\) 对应的区间。
因为 \(s_i\) 既然被保留下来了说明任意它出现的位置它都是最长或次长串,那么 \(s_i\) 在 \(s_j\) 的所有出现位置上都会保留下一个区间。同时也很容易知道如果 \(s_i\) 不是 \(s_j\) 的子串,那么所有 \(s_j\) 的区间里都不可能包含一个 \(s_i\) 的区间。

那么我们只需要判断出 包含 \(s_i\) 的所有区间中至少一个的区间是否只有一种编号即可。这是简单的 二维偏序 问题。

每次扫描线的区间数量和为 \(O(\sum |s_i|)\),总复杂度 \(O(\sum |s_i| \times \log(\sum |s_i|))\)。

CODE:

#include<bits/stdc++.h>
#define pb emplace_back
#define MP make_pair
using namespace std;
typedef pair< int, int > PII;
const int N = 5e5 + 10;
const int M = 1e6 + 10;
int n, res;
char str[M];
vector< char > s[N];
struct BIT {
	int c[M];
	inline int lowbit(int x) {return x & -x;}
	inline void add(int x, int y) {for(; x < M; x += lowbit(x)) c[x] += y;}
	inline int ask(int x) {int res = 0; for(; x; x -= lowbit(x)) res += c[x]; return res;}
} T, F;
struct ACAM {
	int tot, trie[M][26], fail[M], len[M];
	int et[M], mx[M][2]; // 最长长度, 次长长度不为自身
	int L[M], R[M], dfc;
	vector< int > E[M]; // fail 树
	inline void ins(char *s) {
		int l = strlen(s + 1);
		int p = 0;
		for(int i = 1; i <= l; i ++ ) {
			if(!trie[p][s[i] - 'a']) trie[p][s[i] - 'a'] = ++ tot, len[trie[p][s[i] - 'a']] = len[p] + 1;
			p = trie[p][s[i] - 'a'];
		}
		et[p] = 1;
	}
	void dfs(int x) {
		L[x] = ++ dfc; 
		for(auto v : E[x]) dfs(v);
		R[x] = dfc;
	}
	inline void build() {
		queue< int > q;
		for(int i = 0; i < 26; i ++ ) 
			if(trie[0][i]) q.push(trie[0][i]);
		while(!q.empty()) {
			int u = q.front(); q.pop();
			for(int i = 0; i < 26; i ++ ) {
				if(trie[u][i]) {
					fail[trie[u][i]] = trie[fail[u]][i];
					int x = fail[trie[u][i]], y = trie[u][i];
					if(et[x]) mx[y][0] = x;
					if(len[mx[x][0]] > len[mx[y][0]]) swap(mx[y][0], mx[y][1]), mx[y][0] = mx[x][0];
					else mx[y][1] = (len[mx[y][1]] >= len[mx[x][0]] ? mx[y][1] : mx[x][0]);
					mx[y][1] = (len[mx[y][1]] >= len[mx[x][1]] ? mx[y][1] : mx[x][1]);
					q.push(trie[u][i]);
				}
				else trie[u][i] = trie[fail[u]][i];
			}
		}
		for(int i = 1; i <= tot; i ++ ) E[fail[i]].pb(i);
		dfs(0);
	}
} AC;
struct seg {
	int l, r, idx;
} P[M * 2];
inline bool cmp(seg x, seg y) {return (x.l < y.l) || (x.l == y.l && x.r > y.r);}
int cov[M], ed[M], nd[M * 2];
inline int calc(int x) { // 计算第 s[x] 作为最长串的答案
	int p = 0, ct = 0, cn = 0; 
	for(int i = 0; i < s[x].size(); i ++ ) {
		p = AC.trie[p][s[x][i] - 'a'];
		int u = (i != s[x].size() - 1 ? (AC.et[p] ? AC.mx[p][0] : AC.mx[p][1]) : AC.mx[p][1]);
		T.add(AC.L[u], 1);
	}
	p = 0; 
	for(int i = 0; i < s[x].size(); i ++ ) {
		p = AC.trie[p][s[x][i] - 'a'];
		int a = AC.mx[p][0], b = AC.mx[p][1];
		if(i != s[x].size() - 1) {
			if(AC.et[p]) swap(a, b), a = p;
		}
		if(a && T.ask(AC.R[a]) - T.ask(AC.L[a]) == 0) P[++ ct] = (seg) {i + 1 - AC.len[a] + 1, i + 1, a}, nd[++ cn] = a;
		if(b && T.ask(AC.R[b]) - T.ask(AC.L[b]) == 0) P[++ ct] = (seg) {i + 1 - AC.len[b] + 1, i + 1, b}, nd[++ cn] = b;
	}
	sort(nd + 1, nd + cn + 1); cn = unique(nd + 1, nd + cn + 1) - (nd + 1);
	priority_queue< PII > q;
	sort(P + 1, P + ct + 1, cmp);
	for(int i = 1; i <= ct; i ++ ) {
		if(cov[P[i].idx] != -1) {			
			int c = F.ask(M - 1) - F.ask(P[i].r - 1);
			if(c > 1) cov[P[i].idx] = -1;
			else if(c == 1) {
				if(cov[P[i].idx] != 0) {
					if(cov[P[i].idx] != q.top().second) cov[P[i].idx] = -1;
				}
				else cov[P[i].idx] = q.top().second;
			}
		}
		if(ed[P[i].idx] != 0) F.add(ed[P[i].idx], -1);
		ed[P[i].idx] = max(ed[P[i].idx], P[i].r); F.add(ed[P[i].idx], 1);
		q.push(MP(P[i].r, P[i].idx));
	}
	int res = 0;
	for(int i = 1; i <= cn; i ++ ) {res += (cov[nd[i]] > 0); cov[nd[i]] = 0; F.add(ed[nd[i]], -1); ed[nd[i]] = 0;}
	p = 0;
	for(int i = 0; i < s[x].size(); i ++ ) {
		p = AC.trie[p][s[x][i] - 'a'];
		int u = (i != s[x].size() - 1 ? (AC.et[p] ? AC.mx[p][0] : AC.mx[p][1]) : AC.mx[p][1]);
		T.add(AC.L[u], -1);
	}
	return res;
}
int main() {
	scanf("%d", &n);
	for(int i = 1; i <= n; i ++ ) {
		scanf("%s", str + 1);
		AC.ins(str); int m = strlen(str + 1);
		for(int j = 1; j <= m; j ++ ) s[i].pb(str[j]); 
	}
	AC.build();
	for(int i = 1; i <= n; i ++ ) res += calc(i);
	cout << res << endl;
	return 0;
}

posted @ 2026-09-10 14:36  Czl2007  阅读(6)  评论(0)    收藏  举报