1. 程式人生 > >@bzoj - [email protected] [Ctsc2012]Cheat

@bzoj - [email protected] [Ctsc2012]Cheat

目錄


@[email protected]

給定 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|

@[email protected]

顯然二分答案 + dp 判定。
二分出 L,得到 dp[i] = max(dp[i-1], dp[j]+(i-j)),其中 j <= i-L 且 j+1...i 這一段是文字庫某一個串的子串。
這是一個很顯然的單調佇列可以優化的 dp。

因此,我們需要求 A 的每一個字首的某個長度最長的字尾,使這個字尾是文字串的某一個串的子串。
假設文字庫裡面只有一個串,就會讓人想起字尾自動機尋找兩個串的公共子串的過程。也是對於每一個字首求它長度最長的字尾。
對於多個串,我們需要廣義字尾自動機。

廣義字尾自動機,概念上就是對多個串建同一個字尾自動機,實現上就是如果要加入新的字串,就將 last 指標重新移回 root。
如果某個轉移邊以前出現過會怎樣?你會發現新加入的點並不會與其他點連通,因為它不會和它之前的任何結點連邊。

@accepted [email protected]

#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);
    }
}

@[email protected]

本題好像聽他們說,如果你乘 0.9 就會產生浮點誤差,然後就會掛掉。
有這麼卡精度的嗎?才 0.9 啊喂。