【BZOJ 3620】似乎在梦中见过的样子

题目

(夢の中で逢った、ような……)

「Madoka,不要相信 QB!」伴随着 Homura 的失望地喊叫,Madoka 与 QB 签订了契约。

这是 Modoka 的一个噩梦,也同时是上个轮回中所发生的事。为了使这一次 Madoka 不再与 QB 签订契约,Homura 决定在刚到学校的第一天就解决 QB。然而,QB 也是有许多替身的(但在第八话中的剧情显示它也有可能是无限重生的),不过,意志坚定的 Homura 是不会放弃的——她决定消灭所有可能是 QB 的东西。现在,她已感受到附近的状态,并且把它转化为一个长度为 \(n\) 的字符串交给了学 OI 的你。

现在你从她的话中知道,所有形似于 \(A+B+A\) 的字串都是 QB 或它的替身,且 \(|A|\ge k,|B|\ge 1\) (位置不同其他性质相同的子串算不同子串,位置相同但拆分不同的子串算同一子串),然后你必须尽快告诉 Homura 这个答案——QB 以及它的替身的数量。

\(n\le 1.5\times 10^4, k\le100\)

注:本题小常数\(O(n^2)\)可过。

Homura不是有回溯时间的能力么。

分析

这个\(A+B+A\)的定义和KMP中的next数组太像了。

我们枚举左端点,先假设我们要判断\([1,r]\)是否合法。

若当前的next值留不出\(B\)的位置,我们需要缩小范围,因为next数组是最长的相同前后缀的长度,而我们要缩小它。

显然的,若\(\rm next[r-1] = p\),则代表\([1,p]=[r-p,r]\),且任意的相同前缀后缀的长度都不会大于这个\(p\)

我们考虑若有\(q<p,[1,q]=[r-q,r]\),则有\([1,q]=[p-q,p]\),所以问题等价于找\([1,p]\)之间的最长的相同前后缀,为最大的\(q\)(因为我们要让\(|A|\ge k\))。

规模成功缩小,只要找到一个留的出\(B\)\(|A|\ge k\)的一个答案即可统计。

然而,我们发现这样做,时间复杂度是要到\(O(n^3)\)的。

我们只要记录以下满足\(|A|\ge k\)的最小的点,然后直接用就可以了。

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

伪代码

\[\begin{split} & \mathbf{Input} :\text{字符串S}\\ & \mathbf{Output} :\text{返回[0,0],[0,1]... [0,|S|]中有几个这样的子串,并处理next数组}\\ \\ 1 &\ \mathbf{Function} \;\rm GetNext(S) \;\{ \\ 2 &\ \rm \quad j := -1,\; ans := 0 \\ 3 &\ \rm \quad \text{For i in [1,n]} \\ 4 &\ \rm \quad \quad v := j \\ 5 &\ \rm \quad \quad if \; j < k \\ 6 &\ \rm \quad \quad \quad v := \infty \\ 7 &\ \rm \quad \quad if\; j \ge 0 \\ 8 &\ \rm \quad \quad \quad last[i] := min(last[j],\;v) \\ 9 &\ \rm \quad \quad else \\ 10 &\ \rm \quad \quad \quad last[i] := v \\ 11 &\ \rm \quad \quad \text{if } i \le (i - 1) \div 2 \\ 12 &\ \rm \quad \quad \quad ans := ans + 1\\ 13 &\ \rm \quad \quad While\; j \ge 0 \text{ and S[i]}\not =S[j] \\ 14 &\ \rm \quad \quad \quad j := next[j] \\ 15 &\ \rm \quad \quad j := j + 1 \\ 16 &\ \rm \quad \quad next[i + 1] := j \\ 17 &\ \rm \quad return\;ans\\ 18 &\ \} \\ \end{split} \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \quad \]

C++代码

#include <bits/stdc++.h>

using std::cin;
using std::cout;

const int MAXN = 2e4 + 7;
const int inf = 0x3f3f3f3f;

int k;

int getnext(std::string s) {
	static int nxt[MAXN], last[MAXN];
	int ans = 0;
	nxt[0] = -1;
	for(int i = 0, j = -1; i <= s.length(); i++) {
		int v = (j >= k ? j : inf);
		last[i] = (j >= 0 ? std::min(last[j], v) : v);
		ans += (last[i] <= (i - 1) / 2);
		while(j >= 0 && s[j] != s[i]) j = nxt[j];
		nxt[i + 1] = ++j;
	}
	return ans;
}

int main() {
	int ans = 0;
	std::string s;
	cin >> s >> k;
	for(int i = 0; i < s.length(); i++) {
		ans += getnext(s.substr(i));
	}
	cout << ans << std::endl;
	return 0;
}

拓展

NOI 2014 动物园

posted @ 2018-09-01 10:51  zhylj  阅读(261)  评论(0编辑  收藏  举报