JZOJ4774 【GDOI2017模拟9.10】子串 线段树合并维护SAM的fail树信息(CF 666E类似)
2016-09-10 17:26
543 查看
题目大意
有N个字符串,S1,S2...SN。现有有Q个形如(li,ri,Pi)的询问,表示字符串Pi在Sl~Sr中多少个串出现过。N,Q≤5∗105
解题思路
看到这种多串匹配为问题,我们可以考虑用SAM来实现。首先,我们对N个串Si建一颗Trie,在Trie上构SAM,那么每次我们询问一个串Pi时,我们可以找到这个串在SAM中对应的节点(如果没有答案就是0)。然后就是要考虑怎么统计有多少个Si会有这个节点代表的状态。我们对于每个Si的后缀,在代表这个字符串的节点上打一个i的标记,构完SAM后沿fail链把标记上传,现在对于一个节点,我们就要询问它有多少个在li~ri中有多少个不同的标记。这个经典问题可以用线段树和并在O(NLogN)的时间完成。
注意:
1. Trie构SAM要用Bfs来构图复杂度才是对的。
2. 如果不构Trie,不能加入完一个串直接把Last=Root,要在两个串之间加入一个字符集意外的字符,但是这样SAM的点集大小就要乘2(在这题空间限制如果是512M的话就会被卡)。
程序
//YxuanwKeith #include <cstring> #include <cstdio> #include <algorithm> #include <vector> using namespace std; const int MAXN = 1e6 + 5; struct Sam { int Len, Root, Pre, Go[3]; } A[MAXN]; struct Trie { int Go[2], Root; } Tri[MAXN]; struct Tree { int l, r, Sum; } Tr[MAXN * 20]; struct Query { int l, r, bel; Query(int a, int b, int c) {l = a, r = b, bel = c;} Query() {} }; vector<Query> Q[MAXN]; int Lst, lst, Sum, num, Cnt, tot, QQ, N, Root, Get, Side, Ans[MAXN], Ord[MAXN]; char S[MAXN]; void Read(int &x) { char ch = getchar(); while (ch < '0' || ch > '9') ch = getchar(); x = 0; while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar(); } void Sam_Push(int l, int r, int bel, int len) { if (Get < len) return; Q[Side].push_back(Query(l, r, bel)); } void Sam_Go(int c) { for (; lst != Root && !A[lst].Go[c]; lst = A[lst].Pre, Get = A[lst].Len); if (A[lst].Go[c]) lst = A[lst].Go[c], Get ++; Side = lst; } void Prepare() { for (int i = 1; i <= QQ; i ++) { int l, r; Read(l), Read(r); scanf("%s", S + 1); lst = Root, Get = Side = 0; int Len = strlen(S + 1); for (int j = 1; j <= Len; j ++) Sam_Go(S[j] - 'a'); Sam_Push(l, r, i, Len); } } int Sam_Add(int lst, int c, int Rt) { int np = ++ tot, p = lst; A[np].Len = A[lst].Len + 1; A[np].Root = Rt; for (; p && !A[p].Go[c]; p = A[p].Pre) A[p].Go[c] = np; if (!p) A[np].Pre = Root; else { int q = A[p].Go[c]; if (A[q].Len == A[p].Len + 1) A[np].Pre = q; else { int nq = ++ tot; A[nq] = A[q], A[nq].Root = 0; A[nq].Len = A[p].Len + 1; A[np].Pre = A[q].Pre = nq; for (; p && A[p].Go[c] == q; p = A[p].Pre) A[p].Go[c] = nq; } } return np; } void Sort() { static int tax[MAXN]; memset(tax, 0, sizeof tax); for (int i = 1; i <= tot; i ++) tax[A[i].Len] ++; for (int i = 1; i <= tot; i ++) tax[i] += tax[i - 1]; for (int i = tot; i; i --) Ord[tax[A[i].Len] --] = i; } void Tr_Merge(int &Rt, int rt, int ot, int l, int r) { Rt = ++ Cnt; if (!rt) {Tr[Rt] = Tr[ot]; return;} if (!ot) {Tr[Rt] = Tr[rt]; return;} if (l == r) { Tr[Rt].Sum = max(Tr[ot].Sum, Tr[rt].Sum); return; } int Mid = (l + r) >> 1; Tr_Merge(Tr[Rt].l, Tr[rt].l, Tr[ot].l, l, Mid), Tr_Merge(Tr[Rt].r, Tr[rt].r, Tr[ot].r, Mid + 1, r); Tr[Rt].Sum = Tr[Tr[Rt].l].Sum + Tr[Tr[Rt].r].Sum; } void Tr_Add(int &Rt, int rt, int l, int r, int Side) { Rt = ++ Cnt; if (rt) Tr[Rt] = Tr[rt]; if (l == r) { Tr[Rt].Sum = 1; return; } int Mid = (l + r) >> 1; if (Side <= Mid) Tr_Add(Tr[Rt].l, Tr[Rt].l, l, Mid, Side); else Tr_Add(Tr[Rt].r, Tr[Rt].r, Mid + 1, r, Side); Tr[Rt].Sum = Tr[Tr[Rt].l].Sum + Tr[Tr[Rt].r].Sum; } void Tr_Query(int Rt, int l, int r, int lx, int rx) { if (!Rt) return; if (rx < l || lx > r) return; if (l >= lx && r <= rx) { Sum += Tr[Rt].Sum; return; } int Mid = (l + r) >> 1; Tr_Query(Tr[Rt].l, l, Mid, lx, rx), Tr_Query(Tr[Rt].r, Mid + 1, r, lx, rx); } void Solve() { Sort(); for (int i = tot; i; i --) { int Now = Ord[i]; Tr_Merge(A[A[Now].Pre].Root, A[Now].Root, A[A[Now].Pre].Root, 1, N); } for (int i = 1; i <= tot; i ++) { for (int j = 0; j < Q[i].size(); j ++) { int l = Q[i][j].l, r = Q[i][j].r, bel = Q[i][j].bel; Sum = 0; Tr_Query(A[i].Root, 1, N, l, r); Ans[bel] = Sum; } } for (int i = 1; i <= QQ; i ++) printf("%d\n", Ans[i]); } void Trie_Add(int bel, int c) { if (!Tri[Lst].Go[c]) Tri[Lst].Go[c] = ++ num; Lst = Tri[Lst].Go[c]; Tr_Add(Tri[Lst].Root, Tri[Lst].Root, 1, N, bel); } void Sam_Build() { static int D[MAXN][2], top = 1; D[1][0] = 1, D[1][1] = 1; for (int i = 1; i <= top; i ++) { for (int j = 0; j < 2; j ++) { if (!Tri[D[i][1]].Go[j]) continue; int v = Tri[D[i][1]].Go[j]; D[++ top][0] = Sam_Add(D[i][0], j, Tri[v].Root); D[top][1] = v; } } } int main() { scanf("%d%d", &N, &QQ); Root = tot = lst = Lst = num = 1; for (int i = 1; i <= N; i ++) { scanf("\n%s", S + 1); int Len = strlen(S + 1); Lst = 1; for (int j = 1; j <= Len; j ++) Trie_Add(i, S[j] - 'a'); } Sam_Build(); Prepare(); Solve(); }
相关文章推荐
- JZOJ4753【GDOI2017模拟9.4】种树 LCT维护子树信息+换根时维护Dfs序(CC MONOPLOY加强版)
- JZOJ4769 【GDOI2017模拟9.9】graph CDQ分治+用按秩合并维护带撤销的并查集(BZOJ 4025)
- 4774. 【GDOI2017模拟9.10】子串
- JZOJ4479【GDOI2016模拟4.26】游戏 线段树维护多条线段的信息
- 【GDOI2017模拟9.10】子串
- CF 671D Roads in Yusland 线段树维护代价合并的思想
- 【jzoj4982】【GDOI2017模拟2.23】【加密】【sam】
- JZOJ4844. 【GDOI2017模拟11.2】抗拒黄泉 背包合并相同状态优化容斥
- 【jzoj5221】【GDOI2018模拟7.10】【A】【线段树合并】
- JZOJ4417 【HNOI2016模拟4.1】神奇的字符串 线段树维护信息
- CF 671D Roads in Yusland 线段树维护代价合并的思想 ★ ★ ★ ★
- jzoj 1569. 【普及模拟】公共子串
- 【JZOJ4793】【GDOI2017模拟9.21】妮厨的愤怒
- JZOJ4849. 【GDOI2017模拟11.3】记忆的轮廓 期望+答案上界剪枝
- JZOJ4779 【GDOI2017模拟9.14】鞍点(OICamp 2016 Day 5 T1) 计数问题
- JZOJ4858. 【GDOI2017模拟11.4】Walk
- HDU1540 Tunnel Warfare(线段树:维护最大连续子串)
- JZOJ4708. 【NOIP2016提高A组模拟8.20】奇洛金卡达 倒着做的思想+并查集维护
- JZOJ4796 【GDOI2017模拟9.21】三色图 构造可行解
- 【BZOJ4515】游戏,树链剖分+永久化标记线段树维护线段信息(李超线段树)