@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 啊喂。