哈哈哈哈第一次做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;
}