説明
強力な少しは話題と人々ION2018として選ばれました、そして今、彼は件名に名前を付けるの問題を解決する必要があります。
小さな男は話題ION2018として選ばれた、彼は慎重に、被写体の非常に高品質を用意し、そして行われた命名に加えて、作業の対象となっています。
、委員会による毎年命題小文字の文字列を設定し、我々は文字列の名前、その年を呼び出す:IONは、タイトルにも規定の名前であるので、マニュアルION命題の規定は、セッションの数を開催していますので、各質問のために必要名前は、行の名前付き文字列内の非空のサブストリング年でなければならず、いずれかのトピックは、前年と同じ名前することはできません。
いくつかの特別な理由なので、小さなAはION2017の名前に各質問を知らないが、彼は小さなA今そこにある、いくつかの特別な手段で文字列ION2017と名付けました\(Q \)尋ねたら:毎回文字列ION2017の与えられた名前は、文字列ION2018を命名、検索では、名前が委員会の命題の規定を満たさなければならないので、ということは、いくつかのトピックを名付けた名前の空でない部分文字列ION2018連続文字列であることと、任意の一つの同じ主題ION2017に名前を付けていないだろうしています。
いくつかの特別な理由のため、すべてのION2017の問い合わせに与えられた名前は、詳細可視入力形式の文字列の部分文字列の連続したストリングです。
[入力形式
2ライン目の正の整数\(Q \)は、質問の数を表します。
次\(Q \)行、各行は、文字列を有する\(T \)と2つの正の整数\(L、R&LT \)文字列ION2017に名前が付けられているかどうか尋ねるために、プレゼントを\(S [l..r] \)、ION2018命名文字列がある(T \)\、その後、いくつかの命名満たす一定の要件があります。
入力文字列が小文字の構成によって与えられることを確認してください。
[]出力形式の
出力\(Q \)ライン、\(I \)線は非負の整数を表し、\(I \)クエリへの回答を。
[サンプル入力】
scbamgepe
3
smape 2 7
sbape 3 8
sgepe 1 9
[サンプル出力]
12
10
4
データ範囲と[合意]
テストポイント | \(\ Lvert S \ rvert \当量\) | \(Q \当量\) | \(\合計\ lvert T \ rvert \当量\) | 尋ね制限 | その他の制限 |
---|---|---|---|---|---|
1 | \(\ 200) | \(\ 200) | \(40000 \) | すべてのクエリが持っているため(L = 1、R = \ \ lvert S \ rvert \) | \(\ lvert T \ rvert \当量200 \) |
2 | \(\ 1000) | \(\ 200) | \(40000 \) | すべてのクエリが持っているため(L = 1、R = \ \ lvert S \ rvert \) | \(\ lvert T \ rvert \当量200 \) |
3 | \(\ 1000) | \(\ 200) | \(40000 \) | すべてのクエリが持っているため(L = 1、R = \ \ lvert S \ rvert \) | \(\ lvert T \ rvert \当量200 \) |
4 | \(\ 1000) | \(\ 200) | \(5 \回10 ^ 5 \) | すべてのクエリが持っているため(L = 1、R = \ \ lvert S \ rvert \) | ノー |
5 | \(\ 1000) | \(\ 200) | \(5 \回10 ^ 5 \) | すべてのクエリが持っているため(L = 1、R = \ \ lvert S \ rvert \) | ノー |
6 | \(5 \回10 ^ 5 \) | \(1 \) | \(5 \回10 ^ 5 \) | すべてのクエリが持っているため(L = 1、R = \ \ lvert S \ rvert \) | ノー |
7 | \(5 \回10 ^ 5 \) | \(1 \) | \(5 \回10 ^ 5 \) | すべてのクエリが持っているため(L = 1、R = \ \ lvert S \ rvert \) | ノー |
8 | \(^ 5 10 \) | \(^ 5 10 \) | \(2 \回10 ^ 5 \) | すべてのクエリが持っているため(L = 1、R = \ \ lvert S \ rvert \) | ノー |
9 | \(^ 5 10 \) | \(^ 5 10 \) | \(2 \回10 ^ 5 \) | すべてのクエリが持っているため(L = 1、R = \ \ lvert S \ rvert \) | ランダムな文字列 |
10 | \(2 \回10 ^ 5 \) | \(^ 5 10 \) | \(4 \回10 ^ 5 \) | すべてのクエリが持っているため(L = 1、R = \ \ lvert S \ rvert \) | ノー |
11 | \(2 \回10 ^ 5 \) | \(^ 5 10 \) | \(4 \回10 ^ 5 \) | すべてのクエリが持っているため(L = 1、R = \ \ lvert S \ rvert \) | ランダムな文字列 |
12 | \(3 \回10 ^ 5 \) | \(^ 5 10 \) | \(6 \回10 ^ 5 \) | すべてのクエリが持っているため(L = 1、R = \ \ lvert S \ rvert \) | ノー |
13 | \(3 \回10 ^ 5 \) | \(^ 5 10 \) | \(6 \回10 ^ 5 \) | すべてのクエリが持っているため(L = 1、R = \ \ lvert S \ rvert \) | ランダムな文字列 |
14 | \(4 \回10 ^ 5 \) | \(^ 5 10 \) | \(8 \回10 ^ 5 \) | すべてのクエリが持っているため(L = 1、R = \ \ lvert S \ rvert \) | ノー |
15 | \(4 \回10 ^ 5 \) | \(^ 5 10 \) | \(8 \回10 ^ 5 \) | すべてのクエリが持っているため(L = 1、R = \ \ lvert S \ rvert \) | ランダムな文字列 |
16 | \(5 \回10 ^ 5 \) | \(^ 5 10 \) | \(^ 6 10 \) | すべてのクエリが持っているため(L = 1、R = \ \ lvert S \ rvert \) | ノー |
17 | \(5 \回10 ^ 5 \) | \(^ 5 10 \) | \(^ 6 10 \) | すべてのクエリが持っているため(L = 1、R = \ \ lvert S \ rvert \) | ランダムな文字列 |
18 | \(2 \回10 ^ 5 \) | \(^ 5 10 \) | \(^ 6 10 \) | ノー | ノー |
19 | \(3 \回10 ^ 5 \) | \(^ 5 10 \) | \(^ 6 10 \) | ノー | ノー |
20 | \(4 \回10 ^ 5 \) | \(^ 5 10 \) | \(^ 6 10 \) | ノー | ノー |
21 | \(5 \回10 ^ 5 \) | \(^ 5 10 \) | \(^ 6 10 \) | ノー | ノー |
22 | \(5 \回10 ^ 5 \) | \(^ 5 10 \) | \(^ 6 10 \) | ノー | ノー |
23 | \(5 \回10 ^ 5 \) | \(^ 5 10 \) | \(^ 6 10 \) | ノー | ノー |
24 | \(5 \回10 ^ 5 \) | \(^ 5 10 \) | \(^ 6 10 \) | ノー | ノー |
25 | \(5 \回10 ^ 5 \) | \(^ 5 10 \) | \(^ 6 10 \) | ノー | ノー |
对于所有数据,保证 \(1 \leq l \leq r \leq \lvert S \rvert\),\(1 \leq \lvert T \rvert \leq 5 \times 10^5\)。
Solution
先简化题意:求 \(T\) 有多少个本质不同的子串,不是 \(S[l,r]\) 的子串。
考虑对于每个 \(i\) 求出一个最小的 \(j\),满足 \(T[j,i]\) 是 \(S[l,r]\) 的子串,那么对于任意 \(1\leq k<j\) 都满足 \(T[k,i]\) 不是 \(S[l,r]\) 的子串。显然随着 \(i\) 的增大,\(j\) 不会减小。那么考虑双指针做法:一开始令 \(j=1\)。对于每个 \(i\),让 \(j\) 的值不断增加,直到 \(T[j,i]\) 是 \(S[l,r]\) 的子串。
考虑如何快速模拟这个过程:先建出 \(S\) 串的后缀自动机。接着对于每次询问,维护一个指针 \(p\),一开始 \(p\) 指向后缀自动机的根节点。对于每个 \(i\),设 \(p\) 走字符 \(T[i]\) 的边到达的点为 \(u\)。接下来要判断节点 \(u\) 对应的长度为 \(i-j+1\) 字符串是否为 \(S[l,r]\) 的子串。
考虑如何快速判断:一开始建后缀自动机的时候,对每个节点 \(x\) 维护两个标记 \(pos_x,yes_x\)。若 \(x\) 表示的其中一个字符串是 \(S\) 的前缀,前缀长度为 \(len\),则 \(pos_x=len,yes_x=1\),否则 \(pos_x\) 等于 \(parent\) 树上 \(x\) 子树中的任意一个 \(pos_x\),\(yes_x=0\)。显然 \(\lceil\) 节点 \(u\) 对应字符串的 \(right\) 集合\(\rfloor\) 就是 \(\lceil\) \(parent\) 树上 \(u\) 子树中 \(pos_x\) 的并集 \(\rfloor\)。而判断 \(u\) 对应的长度为 \(y\) 的字符串是否是 \(S[l,r]\) 的子串,也就是判断 \(u\) 子树中是否存在一个 \(pos_x∈[l+len-1,r]\)。这个可以用线段树合并或者主席树快速求出。
回到原问题,若节点 \(u\) 对应的长度为 \(i-j+1\) 字符串是 \(S[l,r]\) 的子串,则令 \(p\) 指向节点 \(u\),令 \(lim_i=i-j+1\) 并结束循环。否则令 \(j\) 的值 \(+1\),为了保证 \(p\) 所在的节点能代表字符串 \(T[j,i-1]\),如果 \(i-j=maxl_{fa_p}\),令 \(p=fa_p\)。
因为求的是 \(T\) 中本质不同的合法子串有几个,所以把 \(T\) 串的后缀自动机也建出来。然后枚举这个后缀自动机上的每个节点 \(x\),令答案加上 \(max(0,maxl_x-max(lim_{pos_x},maxl_{fa_x}))\),含义是减去重复出现的子串以及非法的子串。
时间复杂度 \(O(\sum|T|\log|S|)\)。
Code
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int e = 1e6 + 5;
struct point
{
int go[26];
inline void init()
{
memset(go, 0, sizeof(go));
}
}t1[e], t2[e];
struct node
{
int l, r, cnt;
}c[e * 20];
char a[e];
int nxt[e], go[e], adj[e], fa1[e], fa2[e], tot, n, m, maxl1[e], pos[e], id[e];
int lim[e], lst, maxl2[e], num, rt[e], st[e], ed[e], pool, tim;
bool yes[e];
long long ans;
inline void insert1(char ch)
{
int c = ch - 'a', i = lst;
t1[lst = ++tot].init();
maxl1[lst] = maxl1[i] + 1;
yes[lst] = 1;
for (; i && !t1[i].go[c]; i = fa1[i]) t1[i].go[c] = lst;
if (!i) fa1[lst] = 1;
else
{
int j = t1[i].go[c];
if (maxl1[j] == maxl1[i] + 1) fa1[lst] = j;
else
{
int p;
t1[p = ++tot] = t1[j];
fa1[p] = fa1[j];
fa1[j] = fa1[lst] = p;
maxl1[p] = maxl1[i] + 1;
for (; i && t1[i].go[c] == j; i = fa1[i]) t1[i].go[c] = p;
}
}
}
inline void insert2(char ch, int x)
{
int c = ch - 'a', i = lst;
t2[lst = ++tot].init();
maxl2[lst] = maxl2[i] + 1;
pos[lst] = x;
for (; i && !t2[i].go[c]; i = fa2[i]) t2[i].go[c] = lst;
if (!i) fa2[lst] = 1;
else
{
int j = t2[i].go[c];
if (maxl2[j] == maxl2[i] + 1) fa2[lst] = j;
else
{
int p;
t2[p = ++tot] = t2[j];
fa2[p] = fa2[j];
pos[p] = x;
fa2[j] = fa2[lst] = p;
maxl2[p] = maxl2[i] + 1;
for (; i && t2[i].go[c] == j; i = fa2[i]) t2[i].go[c] = p;
}
}
}
inline void add(int x, int y)
{
nxt[++num] = adj[x];
adj[x] = num;
go[num] = y;
}
inline void insert(int y, int &x, int l, int r, int v)
{
c[x = ++pool] = c[y];
c[x].cnt++;
if (l == r) return;
int mid = l + r >> 1;
if (v <= mid) insert(c[y].l, c[x].l, l, mid, v);
else insert(c[y].r, c[x].r, mid + 1, r, v);
}
inline int query(int x, int l, int r, int s, int t)
{
if (!x) return 0;
if (l == s && r == t) return c[x].cnt;
int mid = l + r >> 1;
if (t <= mid) return query(c[x].l, l, mid, s, t);
else if (s > mid) return query(c[x].r, mid + 1, r, s, t);
else
return query(c[x].l, l, mid, s, mid) + query(c[x].r, mid + 1, r, mid + 1, t);
}
inline bool check(int x, int l, int r)
{
return x && query(rt[ed[x]], 1, n, l, r) > query(rt[st[x] - 1], 1, n, l, r);
}
inline void dfs(int u)
{
st[u] = ++tim;
id[tim] = u;
for (int i = adj[u]; i; i = nxt[i]) dfs(go[i]);
ed[u] = tim;
}
inline void build()
{
int i;
for (i = 1; i <= n; i++) insert1(a[i]);
for (i = 2; i <= tot; i++) add(fa1[i], i);
dfs(1);
for (i = 1; i <= tot; i++)
if (yes[id[i]]) insert(rt[i - 1], rt[i], 1, n, maxl1[id[i]]);
else rt[i] = rt[i - 1];
}
int main()
{
int i, l, r;
tot = lst = 1;
scanf("%s", a + 1);
n = strlen(a + 1);
build();
scanf("%d", &m);
while (m--)
{
tot = lst = 1;
t2[1].init();
int p = 1, nowl = 0;
scanf("%s%d%d", a + 1, &l, &r);
int len = strlen(a + 1);
for (i = 1; i <= len; i++)
{
int c = a[i] - 'a';
insert2(a[i], i);
for (;;)
{
if (check(t1[p].go[c], l + nowl, r))
{
nowl++;
p = t1[p].go[c];
break;
}
if (nowl == 0) break;
nowl--;
if (nowl == maxl1[fa1[p]]) p = fa1[p];
}
lim[i] = nowl;
}
ans = 0;
for (i = 2; i <= tot; i++)
ans += max(0, maxl2[i] - max(maxl2[fa2[i]], lim[pos[i]]));
printf("%lld\n", ans);
}
return 0;
}