您的位置:首页 > 大数据 > 人工智能

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();
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: