bzoj 1535 [POI2005]Sza-Template KMP

题面

题目传送门

解法

花了好久终于把这道题弄懂了……

  • 可以发现,满足性质的字符串一定同时是整个串的前缀和后缀
  • 首先考虑这样一个时间复杂度为 O ( n 2 ) O(n^2) O(n2)的做法:
  • 对原串进行KMP求出 n x t nxt nxt数组,那么我们就可以一个一个枚举同时为前缀和后缀的字符串
  • 考虑如何检验答案的合法性,找出该字符串在原串中出现的位置(这里指匹配到的最后一位),将这些位置标记一下。如果相邻标记点的最大距离不超过该字符串的长度,那么这个字符串是一定可以覆盖原串,即为一个合法解
  • 时间复杂度为 O ( n 2 ) O(n^2) O(n2),无法通过此题
  • 接下来考虑如何优化这个过程
  • 可以发现,假设有两个前缀 p r e ( i ) , p r e ( j )    ( i &lt; j ) pre(i),pre(j)\ \ (i&lt;j) pre(i),pre(j)  (i<j),那么如果这个位置可以匹配到 p r e ( j ) pre(j) pre(j) p r e ( i ) pre(i) pre(i)也一定可以被匹配到,反之就不一定是正确的了
  • 那么,如果我们按照从小到大的顺序枚举答案,就会发现匹配的点只会被一个一个地删去,并不会加入一些点
  • 然后发现原来的问题就转化成下面的新问题:如何在到较长答案的时候更新所有匹配点
  • 考虑这样一个做法:建出 f a i l fail fail树,即连接 ( n x t [ i ] , i ) (nxt[i],i) (nxt[i],i),这样显然会形成一棵树。可能满足要求的点一定在 0 0 0 n n n的这一条路径上,假设我们现在做到点 i i i,那么匹配的点一定只可能在 i i i的子树中,只要删除不属于这条路径的 i i i的子树,然后继续做即可
  • 现在大致证明一下为什么匹配的点一定只可能在 i i i的子树中
  • 假设 j j j这个点和 p r e ( i ) pre(i) pre(i)匹配,这里指的是匹配时 p r e ( i ) pre(i) pre(i)的最后一个位置在 j j j上。那么可以发现 p r e ( i ) pre(i) pre(i)既是 p r e ( j ) pre(j) pre(j)的前缀,同时也是 p r e ( j ) pre(j) pre(j)的后缀,所以 i i i一定在 j j j到根的路径上,反过来就说明了 i i i j j j的祖先, j j j i i i的子树中
  • 如果 j j j这个点和 i i i不匹配,那么它一定不在 i i i的子树里,只要删去这个点就可以了
  • 那么,这个算法的正确性已经证明完毕
  • 如何维护删除操作呢?只要使用一个双向链表,在删除的时候顺便维护一下匹配点之间距离的最大值就可以了
  • 因为每一个位置只会被删除至多一次,所以时间复杂度: O ( ∣ S ∣ ) O(|S|) O(S)

代码

#include <bits/stdc++.h>
#define N 500010
using namespace std;
struct Edge {
	int next, num;
} e[N * 3];
int n, mx, ans, cnt, pre[N], nxt[N], used[N], fail[N];
char st[N];
void add(int x, int y) {
	e[++cnt] = (Edge) {e[x].next, y};
	e[x].next = cnt;
}
void kmp() {
	fail[1] = 0;
	for (int i = 2; i <= n; i++) {
		int j = fail[i - 1];
		while (j && st[j + 1] != st[i]) j = fail[j];
		if (st[j + 1] == st[i]) j++; fail[i] = j;
	}
}
void del(int x) {
	pre[nxt[x]] = pre[x], nxt[pre[x]] = nxt[x];
	mx = max(mx, nxt[x] - pre[x]);
	for (int p = e[x].next; p; p = e[p].next)
		del(e[p].num);
}
void dfs(int x) {
	if (mx <= x) return ans = x, void();
	int nxt = 0;
	for (int p = e[x].next; p; p = e[p].next) {
		int k = e[p].num;
		if (used[k]) nxt = k;
			else del(k);
	}
	dfs(nxt);
}
int main() {
	scanf(" %s", st + 1);
	n = strlen(st + 1); cnt = n; kmp();
	for (int i = 1; i <= n; i++)
		add(fail[i], i), pre[i] = i - 1, nxt[i] = i + 1;
	nxt[n] = 0, mx = 1;
	for (int i = n; i; i = fail[i]) used[i] = 1;
	dfs(0); cout << ans << "\n";
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值