@bzoj - 2806@ [Ctsc2012]Cheat
@description@
给定 M 个 01 串表示文本库,再给 N 个询问。
我们称一个子串是 “L - 熟悉” 的,当且仅当这个子串的长度大于等于 L 且是文本库中某一个串的子串。
每次询问给出一个 01 串 A,如果可以把这个串分成若干段子串,其中 “L0 - 熟悉” 的子串长度和 >= 90%*|A|,则称 L0 满足要求。输出满足要求的 L0 最大值。
input
第一行两个整数 N,M。含义如上。
接下来 M 行的 01 串表示文本库。
接下来 N 行的 01 串表示询问。
output
对于每个询问,输出相应的答案。
sample input
1 2
10110
000001110
1011001100
sample output
4
sample explain
|10110|0110|0|
@solution@
显然二分答案 + dp 判定。
二分出 L,得到 dp[i] = max(dp[i-1], dp[j]+(i-j)),其中 j <= i-L 且 j+1...i 这一段是文本库某一个串的子串。
这是一个很显然的单调队列可以优化的 dp。
因此,我们需要求 A 的每一个前缀的某个长度最长的后缀,使这个后缀是文本串的某一个串的子串。
假设文本库里面只有一个串,就会让人想起后缀自动机寻找两个串的公共子串的过程。也是对于每一个前缀求它长度最长的后缀。
对于多个串,我们需要广义后缀自动机。
广义后缀自动机,概念上就是对多个串建同一个后缀自动机,实现上就是如果要加入新的字符串,就将 last 指针重新移回 root。
如果某个转移边以前出现过会怎样?你会发现新加入的点并不会与其他点连通,因为它不会和它之前的任何结点连边。
@accepted code@
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 1100000;
struct sam{
sam *ch[2], *fa; int mx;
}pl[2*MAXN + 5], *tcnt, *root, *lst;
void init() {
tcnt = root = lst = &pl[0];
}
sam *newnode() {
return (++tcnt);
}
void sam_extend(int x) {
sam *cur = newnode(), *p = lst;
cur->mx = lst->mx + 1, lst = cur;
while( p && !p->ch[x] )
p->ch[x] = cur, p = p->fa;
if( !p )
cur->fa = root;
else {
sam *q = p->ch[x];
if( q->mx == p->mx + 1 )
cur->fa = q;
else {
sam *cne = newnode();
(*cne) = (*q), cne->mx = p->mx + 1;
q->fa = cur->fa = cne;
while( p && p->ch[x] == q )
p->ch[x] = cne, p = p->fa;
}
}
}
char s[MAXN + 5];
int f[MAXN + 5], dp[MAXN + 5], len;
int que[MAXN + 5];
bool check(int L) {
for(int i=1;i<L;i++)
dp[i] = 0;
int s = 1, t = 0;
for(int i=L;i<=len;i++) {
while( s <= t && dp[i - L] - (i - L) > dp[que[t]] - que[t] )
t--;
que[++t] = i - L;
while( s <= t && que[s] < i - f[i-1] )
s++;
dp[i] = dp[i-1];
if( s <= t ) dp[i] = max(dp[i], dp[que[s]] + i - que[s]);
}
return 10*dp[len] >= 9*len;
}
int main() {
init();
int N, M; scanf("%d%d", &N, &M);
for(int i=1;i<=M;i++) {
scanf("%s", s); lst = root;
int len = strlen(s);
for(int j=0;j<len;j++)
sam_extend(s[j] - '0');
}
for(int i=1;i<=N;i++) {
scanf("%s", s); len = strlen(s);
int le = 0, ri = len, res = 0; sam *nw = root;
for(int j=0;j<len;j++) {
while( nw && !nw->ch[s[j] - '0'] )
nw = nw->fa;
if( !nw ) res = 0, nw = root;
else res = min(res, nw->mx) + 1, nw = nw->ch[s[j] - '0'];
f[j] = res;
}
while( le < ri ) {
int mid = (le + ri + 1) >> 1;
if( check(mid) ) le = mid;
else ri = mid - 1;
}
printf("%d\n", le);
}
}
@details@
本题好像听他们说,如果你乘 0.9 就会产生浮点误差,然后就会挂掉。
有这么卡精度的吗?才 0.9 啊喂。