广义后缀自动机+二分+单调队列+dp
这道题其实就是一个简单dp,dp[i]表示匹配到i最长匹配多少,设val[i]表示当前位置和原串的最长公共长度,二分的长度是L,那么要求dp[i]=max(dp[i-1],dp[j]+i-j)要求L<=i-j<=val[i],那么也就是j>=i-val[i],前面的l每次把不符合的L>i-j弹掉,由于val[i]每次最多增加1,所以i-val[i]是不增的,所以可以用单调队列维护。最先开始我还想怎么在自动机上dp,结果直接求出val就行了。
#include<bits/stdc++.h> using namespace std; const int N = 2.2e6 + 5; int n, m; int q[N], dp[N], val[N]; char s[N]; namespace SAM { struct node { int val, par; int ch[2]; } t[N]; int last = 1, sz = 1, root = 1; int nw(int _) { t[++sz].val = _; return sz; } void extend(int c) { int p = last, np = nw(t[p].val + 1); while(p && !t[p].ch[c]) t[p].ch[c] = np, p = t[p].par; if(!p) t[np].par = root; else { int q = t[p].ch[c]; if(t[q].val == t[p].val + 1) t[np].par = q; else { int nq = nw(t[p].val + 1); memcpy(t[nq].ch, t[q].ch, sizeof(t[q].ch)); t[nq].par = t[q].par; t[q].par = t[np].par = nq; while(p && t[p].ch[c] == q) t[p].ch[c] = nq, p = t[p].par; } } last = np; } } using namespace SAM; bool check(int L, int n) { int l = 1, r = 0, ans = 0; for(int i = 1; i <= n; ++i) { dp[i] = dp[i - 1]; if(i < L) continue; while(l <= r && dp[i - L] + L > dp[q[r]] + (i - q[r])) --r; q[++r] = i - L; while(l <= r && i - q[l] > val[i]) ++l; if(l > r || i - q[l] < L || i - q[l] > val[i]) continue; dp[i] = max(dp[i - 1], dp[q[l]] + (i - q[l])); } return dp[n] * 10 >= 9 * n; } int main() { scanf("%d%d", &n, &m); for(int i = 1; i <= m; ++i) { last = root; scanf("%s", s + 1); int len = strlen(s + 1); for(int j = 1; j <= len; ++j) extend(s[j] - '0'); } for(int i = 1; i <= n; ++i) { scanf("%s", s + 1); int len = strlen(s + 1), u = root, step = 0; for(int j = 1; j <= len; ++j) { if(t[u].ch[s[j] - '0']) u = t[u].ch[s[j] - '0'], ++step; else { while(u && !t[u].ch[s[j] - '0']) u = t[u].par; if(!u) u = root, step = 0; else { step = t[u].val + 1; u = t[u].ch[s[j] - '0']; } } val[j] = step; } int l = 0, r = len + 1, ans = 0; while(r - l > 1) { int mid = (l + r) >> 1; if(check(mid, len)) l = ans = mid; else r = mid; } printf("%d ", ans); } return 0; }