← 全部文章

CF590E Birthday 题解

本文目录(3 节)
  1. 题意
  2. 题解
  3. 代码

CF590E Birthday

题意

  • 给定 nn 个仅包含 a,b 的字符串。
  • 你需要去掉尽可能少的字符串,使得剩下的字符串中不存在某一个串是另一个串的子串。
  • n≤750n \le 750,∑i=1n∣si∣≤107\sum_{i=1}^n |s_i| \le 10^7。

题解

设 ∑i=1n∣si∣=m\sum_{i=1}^n |s_i| = m。

首先可以 O(m)\mathcal O(m) 建 AC 自动机然后求出所有的子串关系。

显然我们不能暴力跳 fail,这样显然会 T 飞。

注意到子串关系是一个偏序关系,因此我们只需要跳到最近的结尾同时路径压缩即可,那么此时可以建出一个 DAG。

接下来就是 P4298 [CTSC2008]祭祀了,不过还是在这儿完整的写一遍。

题目要求的实际上是这个偏序关系的最长反链,通过 Dilworth 定理可以发现就是 DAG 的最小可重复路径点覆盖。

最小可重复路径点覆盖又可以通过 O(n3)\mathcal O(n^3) 的传递闭包转化为最小路径点覆盖。

又 DAG 的最小路径点覆盖包含的路径条数 =n−= n - 其拆点二分图的最大匹配数。

使用匈牙利算法即可在 O(n3)\mathcal O(n^3) 的时间求出答案。

接下来的问题是如何构造方案。

我们先求出传递闭包后的图的最小路径点覆盖包含的路径集合 pathpath:

  1. 设在拆点二分图中左部点 xx 对应的右部点为 x′x^{\prime},若 x,yx,y 匹配则有 fx=y,fy=xf_x = y, f_y = x。
  2. 依次考虑左部的每一个非匹配点 x0x_0。
  3. 从 x0x_0 出发,每次从 xx 走到 fx′f_{x^{\prime}},直至到达一个左部点 y0y_0,满足 y0′y_0^{\prime} 是非匹配点。
  4. 那么经过的所有点构成一条以 y0y_0 为起点 x0x_0 为终点的路径。

接下来我们要从 pathpath 的每条路径上选出一个点构成原图的最长反链:

  1. 将所有的终点 x0x_0 放到一起构成一个集合 EE。
  2. 求出从 EE 中的所有节点出发,走一条边,到达的所有节点 next(E)next(E)。
  3. 根据传递闭包的性质,若 EE 与 next(E)next(E) 没有交,那么 EE 即为所求。
  4. 否则考虑 E∩next(E)E \cap next(E) 的所有节点 ee,沿着 ee 所在的路径反着走,直到一个节点 e′∉next(E)e^{\prime} \notin next(E),在 EE 中将 ee 替换为 e′e^{\prime}。
  5. 回到第 33 步。

总时间复杂度 O(m+n3)\mathcal O(m + n^3)。

注意本题任何与 AC 自动机相关的递归均会爆栈。

代码

const int N = 757, M = 1e7 + 7;
int n, len[N], ed[N], trie[M][2], fail[M], fa[M], gt[M], tot = 1;
int d[N][N], v[N], f[N];
char s[M];
queue<int> q;

bool dfs(int x) {
	for (int i = 1; i <= n; i++)
		if (d[x][i] && !v[i]) {
			v[i] = 1;
			if (!f[i] || dfs(f[i])) return (f[i] = x);
		}
	return 0;
}

int main() {
	rd(n);
	for (int i = 1; i <= n; i++) {
		rds(s, len[i]);
		int p = 1;
		for (int j = 1; j <= len[i]; j++) {
			int c = s[j] - 'a';
			if (!trie[p][c]) trie[p][c] = ++tot, fa[tot] = p;
			p = trie[p][c];
		}
		ed[i] = p, gt[p] = i;
	}
	for (int i = 0; i < 2; i++)
		if (trie[1][i]) fail[trie[1][i]] = 1, q.push(trie[1][i]);
		else trie[1][i] = 1;
	while (q.size()) {
		int x = q.front();
		q.pop();
		for (int i = 0; i < 2; i++)
			if (trie[x][i])
				fail[trie[x][i]] = trie[fail[x]][i], q.push(trie[x][i]);
			else trie[x][i] = trie[fail[x]][i];
	}
	gt[1] = -1;
	vi o;
	for (int i = 1; i <= n; i++)
		for (int j = ed[i]; j != 1; j = fa[j]) {
			int x = fail[j];
			o.clear();
			while (!gt[x]) o.pb(x), x = fail[x];
			while (o.size()) fail[o.back()] = x, o.pop_back();
			fail[j] = x;
			if (j != ed[i] && gt[j]) x = j;
			if (x != 1) d[i][gt[x]] = 1;
		}
	for (int k = 1; k <= n; k++)
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= n; j++)
				d[i][j] |= d[i][k] & d[k][j];
	for (int i = 1; i <= n; i++) d[i][i] = 0;
	int ans = n;
	for (int i = 1; i <= n; i++) ans -= dfs(i), memset(v, 0, sizeof(v));
	print(ans);
	for (int i = 1; i <= n; i++) v[f[i]] = 1;
	vi s;
	for (int i = 1; i <= n; i++) if (!v[i]) s.pb(i);
	memset(v, 0, sizeof(v));
	bool ok = 1;
	while (ok) {
		ok = 0;
		for (int x : s)
			for (int y = 1; y <= n; y++)
				if (d[x][y]) v[y] = 1;
		for (int &x : s)
			if (v[x]) {
				ok = 1;
				while (v[x]) x = f[x];
			}
	}
	for (auto x : s) print(x, ' ');
	return 0;
}

标签:AC 自动机、二分图、构造

评论

    填写昵称即可留言。评论经站主审核后公开,请勿提交私人信息。

    正在连接评论服务…