本蒟蒻屑的第一篇題解博客Qwq
題目翻譯:給定一個由1-9組成的長度范圍為(3e5)的字符串,並給出q(q <= 3e5)次詢問,每次給出個長度k, 求出該字符串中長度至少為k的子串的最長長度。
省賽的C題,比賽時讀題發現應該是一個后綴自動機但由於本屑非oier,對於xx自動機也只是聽過沒有了解,當場就放棄了,近幾天學了SAM的黑盒使用后,終於A了此題。
思路:眾所周知,對一個字符串建起來SAM后,SAM上每個結點都有個l數組,l數組代表的是該節點所包含的字符串集合中,最長字符串的長度,而每個節點有size數組,表示該字符串集合出現的次數。這時候就有個較為朴素的思路:對於每次詢問,枚舉所有tot的節點i,當l[i] >= k時,則ans = max(l[i], ans); 但這樣明顯時間復雜度會超,因此,我們可以預處理出每個點的答案,最后一並詢問。
我們開兩個數組,dp數組和ans數組,其中ans數組為我們要求的答案,而dp數組定義如下:
dp[i] 表示 ans[1] ~ ans[i] 至少應該為幾,
在枚舉每個節點的時候,該節點代表的字符串集合中,最長的長度為l[i],則ans[1] ~ ans[l[i]] 至少為size[i];
則有如下代碼:
for (int i = 1; i <= tot; i++) {
dp[l[i]] = max(dp[l[i]], (ll)size[i]);
}
之后,我們一步一步求ans數組
顯然有,dp[i]為長度為i的子串最大出現次數
則有從后往前推的遞推式,ans[i] = max(dp[i], ans[i + 1]);
則有如下代碼
for (int i = n - 1; i >= 1; i--) {
ans[i] = max(dp[i], ans[i + 1]);
}
完整代碼如下
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 3e5 + 10;
char s[N];
int n, K, q;
ll dp[N];
ll ans[N];
struct SuffixAutoMaton {
int ch[N << 1][10], fa[N << 1],l[N << 1],size[N << 1],k[N << 1],c[N << 1];
int last, tot;
void init() { last = tot = 1;memset(ch[1], 0, sizeof ch[1]); }
void ins(int c,int pos) {
int p = last,np = ++tot;last = np;l[np] = l[p] + 1;
memset(ch[tot],0,sizeof ch[tot]);
for(;p && !ch[p][c]; p = fa[p])ch[p][c] = np;
if(!p)fa[np] = 1;
else {
int q = ch[p][c];
if(l[p] + 1 == l[q]) fa[np] = q;
else {
int nq = ++tot; l[nq] = l[p] + 1;
memcpy(ch[nq], ch[q], sizeof(ch[q]));
fa[nq] = fa[q]; fa[q] = fa[np] = nq;
for(;ch[p][c] == q; p = fa[p]) ch[p][c] = nq;
}
}
size[np] = 1;
}
void build() {
init();
int n = strlen(s + 1);
for(int i = 1;i <= n; i++) ins(s[i]-'0',i);
for(int i = 1;i <= tot; i++) c[l[i]]++;
for(int i = 1;i <= tot; i++) c[i] += c[i-1];
for(int i = 1;i <= tot; i++) k[c[l[i]]--] = i;
for(int i = tot; i >= 1; i--) {
int id = k[i];
size[fa[id]] += size[id];
}
for (int i = 1; i <= tot; i++) {
dp[l[i]] = max(dp[l[i]], (ll)size[i]); // 求出dp數組。
}
}
}sam;
int main() {
cin >> n >> K;
scanf("%s", s + 1);
sam.build();
ans[n] = dp[n];
for (int i = n - 1; i >= 1; i--) {
ans[i] = max(dp[i], ans[i + 1]); //求出ans數組。
}
for (int i = 1; i <= K; i++) {
scanf("%lld", &q);
printf("%lld\n", ans[q]);
}
return 0;
}