Codeforces 666E Forensic Examination (字尾自動機 + 線段樹合併)
阿新 • • 發佈:2020-08-04
題解:首先廣義 \(SAM\) , 然後分析,每次查詢需要查的字串是確定,並且給了你右端點,所以我們在插入文字串 (一開始給的字串) 時,記錄一下第 \(i\) 個字元插入時所對應的節點,然後我們根據字尾連結往上爬,直到爬到一個節點 \(p\) 滿足的 \(minLen[p] \leq p_r - p_l + 1 \leq maxLen[p]\) ,這個節點就包含了我們要查的字串,這個過程可以用倍增實現,複雜度為 \(log(n)\) ,此外,我們還要知道每個節點中每個模式串的貢獻,所以我們對每個節點建立一顆線段樹,記錄每個模式串有多少個屬於該節點的 \(endpoint\) ,每個節點包含的某模式串的 \(endpoint\)
#include <bits/stdc++.h> using namespace std; #define fi first #define se second typedef long long LL; typedef pair<int, int> pii; const int MAXE = 2e6 + 50; int n, m; struct Edge { int to, next; } edge[MAXE * 2]; int k, head[MAXE]; void add(int a, int b){ edge[k].to = b; edge[k].next = head[a]; head[a] = k++; } struct segementTree { static const int maxn = 3e6 + 60; int tree[maxn << 2], ls[maxn << 2], rs[maxn << 2], val[maxn << 2]; int root[maxn << 2]; int sz = 0; void init() {sz = 0;}; void PushUp(int rt){ // 向上合併 if(tree[ls[rt]] == tree[rs[rt]]){ tree[rt] = tree[ls[rt]]; val[rt] = min(val[ls[rt]], val[rs[rt]]); } else if(tree[ls[rt]] > tree[rs[rt]]){ tree[rt] = tree[ls[rt]]; val[rt] = val[ls[rt]]; } else { tree[rt] = tree[rs[rt]]; val[rt] = val[rs[rt]]; } } void insert(int le, int ri, int pos, int &rt){ // 模式串pos在該節點的貢獻 + 1 if(!rt) rt = ++sz; if(le == ri) { tree[rt]++; val[rt] = le; return; } int mid = (le + ri) >> 1; if(pos <= mid) insert(le, mid, pos, ls[rt]); else insert(mid + 1, ri, pos, rs[rt]); PushUp(rt); } int merge(int le, int ri, int u, int v){ // 線段樹合併, 這裡選擇新開一個節點,而不是覆蓋,因為空間允許,不需要將查詢離線 if(!u || !v) return u | v; int now = ++sz; if(le == ri){ tree[now] = tree[u] + tree[v]; val[now] = le; return now; } int mid = (le + ri) >> 1; ls[now] = merge(le, mid, ls[u], ls[v]); rs[now] = merge(mid + 1, ri, rs[u], rs[v]); PushUp(now); return now; } void dfs(int u, int pre){ for(int i = head[u]; i != -1; i = edge[i].next){ int to = edge[i].to; if(to == pre) continue; dfs(to, u); root[u] = merge(1, m, root[u], root[to]); } } pii Query(int le, int ri, int L, int R, int rt){ if(L <= le && ri <= R){ return pii{tree[rt], val[rt]}; } int mid = (le + ri) >> 1; pii ans = {0, 0}; if(L <= mid) { pii res = Query(le, mid, L, R, ls[rt]); if(ans.fi < res.fi) ans = res; } if(R > mid){ pii res = Query(mid + 1, ri, L, R, rs[rt]); if(ans.fi < res.fi) ans = res; } return ans; } } seTree; struct ex_SAM { static const int maxn = 2e6 + 60; int nex[maxn][26], len[maxn], link[maxn], pos[maxn]; int tot, char_num = 26, custr = 0; void init() {tot = 1, link[0] = -1;} int insert_SAM(int last, int c){ int cur = nex[last][c], p = link[last]; len[cur] = len[last] + 1; while(p != -1){ if(!nex[p][c]) nex[p][c] = cur; else break; p = link[p]; } if(p == -1) { link[cur] = 0; return cur; } int q = nex[p][c]; if(len[p] + 1 == len[q]) {link[cur] = q; return cur;} int clone = tot++; for(int i = 0; i < char_num; i++){ nex[clone][i] = len[nex[q][i]] != 0 ? nex[q][i] : 0; } len[clone] = len[p] + 1; while(p != -1 && nex[p][c] == q){ nex[p][c] = clone, p = link[p]; } link[clone] = link[q], link[cur] = clone, link[q] = clone; return cur; } int insertTire(int cur, int c){ // 先建立字典樹 if(!nex[cur][c]) nex[cur][c] = tot++; return nex[cur][c]; } void insert(string s){ custr++; int root = 0; for(auto ch : s) { root = insertTire(root, ch - 'a'); seTree.insert(1, m, custr, seTree.root[root]); // 該節點模式串custr的endpoint數量 + 1 } } void insert2(string s){ // 文字串插入字典樹 int root = 0; int sz = s.size(); for(int i = 0; i < sz; i++) { root = insertTire(root, s[i] - 'a'); pos[i] = root; // 記錄文字串每個endpoint對應的最初的節點(也就是最深的擁有以 i 為結尾的點) } } void Build(){ // 建立字尾自動機 queue<pii> que; for(int i = 0; i < char_num; i++){ if(nex[0][i]) que.push({i, 0}); } while(que.size()){ auto item = que.front(); que.pop(); auto last = insert_SAM(item.se, item.fi); for(int i = 0; i < char_num; i++){ if(nex[last][i]) que.push({i, last}); } } } void BuildTree(){ // 建立link樹 for(int i = 0; i < tot; i++) head[i] = -1; for(int i = 1; i < tot; i++){ add(i, link[i]); add(link[i], i); } } int fa[maxn][30], depth[maxn]; void dfs(int u, int pre, int d){ fa[u][0] = pre, depth[u] = d; for(int i = head[u]; i != -1; i = edge[i].next){ int to = edge[i].to; if(to == pre) continue; dfs(to, u, d + 1); } } void init(int root){ // 倍增處理lca dfs(root, -1, 0); for(int j = 0; (1 << (j + 1)) < tot - 1; j++){ for(int i = 1; i <= tot - 1; i++){ if(fa[i][j] < 0) fa[i][j + 1] = -1; else fa[i][j + 1] = fa[fa[i][j]][j]; } } } int Find(int x, int nlen){ int u = pos[x]; for(int i = 20; i >= 0; i--){ if(fa[u][i] != -1 && len[fa[u][i]] >= nlen) { u = fa[u][i]; } } return u; } } exSam; string s, t; int main(int argc, char const *argv[]) { cin >> s; exSam.init(); exSam.insert2(s); cin >> m; for(int i = 1; i <= m; i++){ cin >> s; exSam.insert(s); } exSam.Build(); exSam.BuildTree(); exSam.init(0); seTree.dfs(0, -1); cin >> n; while(n--){ int le, ri, L, R; scanf("%d%d%d%d", &le, &ri, &L, &R); L--, R--; int len = R - L + 1; int u = exSam.Find(R, len); pii res = seTree.Query(1, m, le, ri, seTree.root[u]); if(res.se == 0) res.se = le;// 坑點,所選模式串不存在該字串,則要輸出le printf("%d %d\n", res.se, res.fi); } return 0; }