《原神攻略》楓原萬葉培養材料收集攻略
題目連結
題目大意
有一個串 \(S\) 和 \(m\) 個串 \(T_i\),\(q\) 次詢問,每次給出 \(l,r,p_l,p_r\),求 \(\max_{l\leq i\leq r}\{freq(T_i,S[p_l,p_r])\}\),即 \(S\) 下標在 \([p_l,p_r]\) 的子串在序號 \(\in[l,r]\) 中的 \(T_i\) 的最大出現次數,輸出滿足最大次數的最小的下標 \(i\) 以及最大出現次數。
\(1\leq|S|,q\leq 5\times10^5\),\(1\leq m,\sum|T_i|\leq 5\times 10^4\)
思路
看到出現次數,果斷後綴自動機(連樣例裡都寫了是字尾資料結構),由於是要把 \(S\) 的子串放 \(T_i\) 裡面算出現次數,顯然把 \(T_i\) 拿出來建 \(SAM\) 會方便一些,有一個常見處理方式,我們將 \(T_i\) 串起來,之間用特殊字元相連構成一個大串 \(T\),然後對 \(T\) 建 \(SAM\) 。
注意到 \(SAM\) 上節點的右端點(集合)是固定的,而左端點管一段區間,那麼這裡也將 \(S\) 子串的右端點固定下來,對於每個右端點 \(r\),先預處理出 \(S[0,r]\) 在 \(SAM\) 上的最大匹配長度 \(len_r\) 和對應的狀態 \(st_r\)
當 \(len_{p_r}\geq p_r-p_l+1\) 的時候,我們嘗試找到 \(S[p_l,p_r]\) 對應的狀態,由於 \(Parent\;Tree\) 到根的樹鏈上的 \(Max(s)\) 是連續減小的,所以一直跳 \(par(st)\),直到 \(Max(par(st))<p_r-p_l+1\),此時必然有 \(p_r-p_l+1\in[Min(st),Max(st)]\),\(st\) 即為對應的狀態,而向上跳的過程可以倍增預處理,做到 \(O(nlogn)\)
現在我們要求的就是在 \(Right(st)\) 裡,\([l,r]\) 內哪個 \(T_i\) 對應的位置最多,這個顯然可以用線段樹維護,我們在 \(SAM\) 每一個節點上開一棵權值線段樹,在建好 \(Parent\;Tree\) 後自底向上更新 \(Right\) 資訊,每次進行線段樹合併即可,預處理 \(O(nlogn)\)。
綜上,時間複雜度為 \(O((n+q)logn)\)
實現細節
-
由於每個節點的線段樹後來都可能被訪問到,所以要採用新建點的方式進行線段樹合併,空間複雜度有 \(2\) 倍左右的常數,實際上要開的再大一點,開剛好 \(2\) 倍會 RE。
-
本人對字串匹配時的暴力跳 \(fail\) 做了一點優化,儘管實測沒啥用,但還是說一下:
對每個節點維護一個屬性 \(frt_\sum\),表示第一個 \(Trans(s,c)\neq null\) 的祖先,這個在 \(dfs\) \(Parent\;Tree\) 的時候自頂向下維護一下即可,這樣匹配轉移時可以做到嚴格 \(O(1)\) 。
-
有一個非常離譜的卡常,開始讀入寫的是 \(cin\) 去同步,後來改成了 \(scanf\) 和數字快讀,效果圖:
(為什麼可以快這麼多)
Code
#include<iostream>
#include<cstring>
#include<cstdio>
#define mem(a,b) memset(a, b, sizeof(a))
#define rep(i,a,b) for(int i = (a); i <= (b); i++)
#define per(i,b,a) for(int i = (b); i >= (a); i--)
#define N 505000
#define T 50500
#define LOG 14
#define PII pair<int, int>
#define fr first
#define sc second
#define t(x) SAM.t[x]
using namespace std;
inline int read(){
int s = 0, w = 1;
char ch = getchar();
while(ch < '0' || ch > '9'){ if(ch == '-') w = -1; ch = getchar(); }
while(ch >= '0' && ch <= '9') s = (s<<3)+(s<<1)+(ch^48), ch = getchar();
return s*w;
}
struct Segment_Tree{
struct node{
int ls, rs;
PII mx;
node(){ ls = rs = 0, mx = {0, 0}; }
} t[4*T*LOG];
int cnt;
Segment_Tree(){ cnt = 0; }
void update(int x){
t[x].mx = max(t[t[x].ls].mx, t[t[x].rs].mx);
}
void insert(int &x, int pos, int l, int r){
x = ++cnt;
if(l == r){ t[x].mx = {1, -pos}; return; }
int mid = (l+r)>>1;
if(pos <= mid) insert(t[x].ls, pos, l, mid);
else insert(t[x].rs, pos, mid+1, r);
update(x);
}
inline int merge(int a, int b, int l, int r){
if(!a || !b) return a+b;
register int root = ++cnt;
if(l == r){
t[root].mx = t[a].mx, t[root].mx.fr += t[b].mx.fr;
} else{
int mid = (l+r)>>1;
t[root].ls = merge(t[a].ls, t[b].ls, l, mid);
t[root].rs = merge(t[a].rs, t[b].rs, mid+1, r);
update(root);
}
return root;
}
inline PII query(int x, int l, int r, int left, int right){
if(!x) return {-1, 0};
if(l >= left && r <= right) return t[x].mx;
register int mid = (l+r)>>1;
register PII ret = {-1, 0};
if(mid >= left) ret = max(ret, query(t[x].ls, l, mid, left, right));
if(mid < right) ret = max(ret, query(t[x].rs, mid+1, r, left, right));
return ret;
}
} SGT;
int m;
char s[N], t[T];
struct Suffix_Automaton{
struct state{
int len, link, root, par[27];
int next[27], up[LOG];
} t[N<<1];
int cnt, lst;
void init(){ cnt = lst = 1; }
void extend(int c, int id){
int cur = ++cnt;
t[cur].len = t[lst].len+1;
if(id) SGT.insert(t[cur].root, id, 1, m);
int u = lst;
while(u && !t[u].next[c])
t[u].next[c] = cur, u = t[u].link;
if(u == 0) t[cur].link = 1;
else{
int q = t[u].next[c];
if(t[q].len == t[u].len+1) t[cur].link = q;
else{
int nq = ++cnt;
t[nq].len = t[u].len+1;
memcpy(t[nq].next, t[q].next, sizeof(t[nq].next));
t[nq].link = t[q].link;
t[q].link = t[cur].link = nq;
while(u && t[u].next[c] == q)
t[u].next[c] = nq, u = t[u].link;
}
}
lst = cur;
}
} SAM;
int head[N*2], to[2*N], nxt[2*N];
int cnt;
int state[N], len[N];
void init(){ mem(head, -1), cnt = -1; }
void add_e(int a, int b){
nxt[++cnt] = head[a], head[a] = cnt, to[cnt] = b;
}
void dfs(int x){
rep(c,0,26){
if(!t(x).next[c]) t(x).par[c] = t(t(x).link).par[c];
else t(x).par[c] = x;
}
t(x).up[0] = t(x).link;
rep(i,1,13) t(x).up[i] = t(t(x).up[i-1]).up[i-1];
for(int i = head[x]; ~i; i = nxt[i]){
int y = to[i];
dfs(y), t(x).root = SGT.merge(t(x).root, t(y).root, 1, m);
}
}
int main(){
scanf("%s %d", s, &m);
SAM.init();
rep(i,1,m){
scanf("%s", t);
int siz = strlen(t);
rep(j,0,siz-1) SAM.extend(t[j]-'a', i);
if(i != m) SAM.extend(26, 0);
}
init();
rep(i,1,SAM.cnt) add_e(t(i).link, i);
dfs(1);
int st = 1, l = 0, n = strlen(s);
rep(i,0,n-1){
int c = s[i]-'a';
if(t(st).next[c]) l++, st = t(st).next[c];
else{
if(t(st).par[c]){
st = t(st).par[c], l = t(st).len+1;
st = t(st).next[c];
} else st = 1, l = 0;
}
len[i+1] = l, state[i+1] = st;
}
int q, r, pl, pr;
q = read();
while(q--){
l = read(), r = read(), pl = read(), pr = read();
if(len[pr] < pr-pl+1) printf("%d 0\n", l);
else{
st = state[pr];
per(i,13,0) if(t(t(st).up[i]).len >= pr-pl+1) st = t(st).up[i];
PII ans = SGT.query(t(st).root, 1, m, l, r);
if(ans.fr == -1) ans = {0, -l};
printf("%d %d\n", -ans.sc, ans.fr);
}
}
return 0;
}