题目链接: Prefix
大致题意
给出n个字符串, 编号从1~n.
每次询问编号在[l, r]区间的所有字符串中, 不同前缀的数目. (强制在线)
解题思路
思维
首先考虑到字符串的总长度为1E5, 因此不同前缀的数量最多为1E5.
如果不加以区间的限制, 询问所有的字符串有多少不同的前缀. 则我们可以通过Trie来完成. 我们把字符串插入Trie中, 如果此时我们为当前字符开辟了新节点, 则不同前缀数目+1.
考虑区间限制, 如果我们把不同的前缀 修改为 不同的数字(即: 询问[l, r]区间不同数字的数目), 则这个题变成了HH的项链, 只不过是强制在线了, 我们可以通过主席树来解决.
主席树 + Tire
我们考虑通过Trie树来存储字符串, 对于每个节点我们都存储上一次出现的位置.
这样用HH的项链中的解法三来维护区间不同数字的个数即可.
AC代码
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 1E5 + 5, L = 0, R = N - 5;
int root[N], ind;
struct node {
int l, r;
int val;
}t[N * 18];
int build(int a, int c, int tl, int tr, int p) {
int x = ++ind; t[x] = t[p];
t[x].val += c;
if (tl == tr) return x;
int mid = tl + tr >> 1;
if (a <= mid) t[x].l = build(a, c, tl, mid, t[p].l);
else t[x].r = build(a, c, mid + 1, tr, t[p].r);
return x;
}
int ask(int l, int r, int tl, int tr, int p, int x) {
if (l <= tl and r >= tr) return t[x].val - t[p].val;
int mid = tl + tr >> 1;
int res = 0;
if (l <= mid) res += ask(l, r, tl, mid, t[p].l, t[x].l);
if (r > mid) res += ask(l, r, mid + 1, tr, t[p].r, t[x].r);
return res;
}
void initct(int n) {
rep(i, n) root[i] = 0;
rep(i, ind) t[i] = { 0, 0, 0 };
ind = 0;
}
namespace {
int t[N][26], ind;
int pos[N];
void insert(int a, const string& s) {
int x = 0; int pre = a - 1;
for (auto& op : s) {
char c = op - 'a';
if (!t[x][c]) t[x][c] = ++ind;
x = t[x][c];
root[a] = build(pos[x], 1, L, R, root[pre]);
pre = a; pos[x] = a;
}
}
void clear(int x = 0) {
for (int i = 0; i < 26; ++i) {
if (t[x][i]) {
pos[t[x][i]] = 0;
clear(t[x][i]), t[x][i] = 0;
}
}
}
void inittrie() { ind = 0, clear(); }
}
char s[N];
int main()
{
int n;
while (~scanf("%d", &n)) {
inittrie(), initct(n);
rep(i, n) {
scanf("%s", s);
insert(i, s);
}
int m; scanf("%d", &m);
int res = 0;
rep(i, m) {
int l, r; scanf("%d %d", &l, &r);
l = (l + res) % n + 1, r = (r + res) % n + 1;
if (l > r) swap(l, r);
printf("%d\n", res = ask(0, l - 1, L, R, root[l - 1], root[r]));
}
}
return 0;
}