CTSC2012 熟悉的文章

哈哈哈哈第一次做CTSC的题超开心,感觉一下子就有档次了许多。

这题的数据范围对我这种不会算空间的蒟蒻就是个迷。

对于 100%的测试数据,输入文件的长度不超过 1100000 字节

呵。

做法:

①把所有作文库连起来,每两个中间用2隔开建SAM

②作文串在SAM上跑一跑,计算出以每一个位置为结尾,最多能匹配多长。即,val[i]表示,S[1…i]中与作文库能匹配的最长suffix的长度。

③由于这题的答案具有可二分性,所以二分一个答案L进行检验。用dp去求最长覆盖数即可(单调队列优化一下)。

这里给出使用单调队列的充分性的证明:即是要证i-val[i]单调不减。

=> i + 1 - val[i + 1] ≥ i - val[i]
=> val[i + 1] ≤ val[i] + 1 显然成立

这题我tle了一整天,愣是卡在90分。人家网上别的程序,最长时间的样例才100多ms,我不算t的点,最大的就有将近700ms。反省了很久,发现是memset的锅。我给单调队列清了0,这样做完全没有必要。但是我是真的没想到会这样。毕竟dp就是一个O(n)的,memset也是O(n)。这启发我们,尽量想清楚再决定写不写有没有意义。

Code:(放心看吧,网上的几组hack数据hack不掉我)

#include <cstdio>
#include <cstring>
#define N 2300010
#include <algorithm>
using namespace std;
int n, m; char s[N];
int ch[N][3], len[N], link[N];
int last, sz;
inline void sam_init() {last = sz = 1; link[1] = 0; len[1] = 0;}
inline void sam_extend(int c) {
	int cur = ++sz; len[cur] = len[last] + 1;
	int p;
	for(p = last; p && !ch[p][c]; p = link[p]) ch[p][c] = cur;
	if(!p) link[cur] = 1;
	else {
		int q = ch[p][c];
		if(len[p] + 1 == len[q]) link[cur] = q;
		else {
			int clone = ++sz; len[clone] = len[p] + 1; link[clone] = link[q];
			for(int i = 0; i < 3; ++i) ch[clone][i] = ch[q][i];
			for(; p && ch[p][c] == q; p = link[p]) ch[p][c] = clone;
			link[q] = link[cur] = clone;
		}
	}
	last = cur;
}
int val[N], f[N], Q[N];
inline bool check(int x, int len1) {
	int head = 1, tail = 0; f[0] = 0;
	for(int i = 1; i <= len1; ++i) {
		f[i] = f[i - 1];
		int p = i - x;
		if(p >= 0) {
			while(head <= tail && p - f[p] < Q[tail] - f[Q[tail]]) --tail;
			Q[++tail] = p;
		}
		while(head <= tail && Q[head] < i - val[i]) ++head;
		if(head <= tail) f[i] = max(f[i], f[Q[head]] - Q[head] + i);
	}
	return 10 * f[len1] >= 9 * len1;
}
inline int read() {  
    int x = 0, f = 1; char ch = getchar();  
    while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}  
    while(ch >= '0' && ch <= '9') {x = x * 10 + ch - 48; ch = getchar();}  
    return x * f;  
}
int main() {
	n = read(); m = read(); sam_init();
	for(int i = 1; i <= m; ++i) {
		scanf("%s", s+1); int l1 = strlen(s+1);
		for(int j = 1; j <= l1; ++j) sam_extend(s[j] - '0');
		sam_extend(2);
	}
	for(int i = 1; i <= n; ++i) {
		scanf("%s", s+1); int len1 = strlen(s+1);
		int o = 1, cur = 0, l = 1, r = 0, ans = 0;
		for(int i = 1; i <= len1; ++i) {
			int c = s[i] - '0';
			if(ch[o][c]) ++cur, o = ch[o][c];
			else {
				while(o && !ch[o][c]) o = link[o];
				if(!o) cur = 0, o = 1;
				else cur = len[o] + 1, o = ch[o][c];
			}
			val[i] = cur; r = max(r, val[i]);
		}
		while(l <= r) {
			int mid = (l + r)>>1;
			if(check(mid, len1)) {ans = mid; l = mid + 1;}
			else r = mid - 1;
		}
		printf("%d\n", ans);
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值