实力强大的小A 被选为了ION2018 的出题人,现在他需要解决题目的命名问题。
小A 被选为了ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外的工作都做好了。
由于ION 已经举办了很多届,所以在题目命名上也是有规定的,ION 命题手册规定:每年由命题委员会规定一个小写字母字符串,我们称之为那一年的命名串,要求每道题的名字必须是那一年的命名串的一个非空连续子串,且不能和前一年的任何一道题目的名字相同。
由于一些特殊的原因,小A 不知道ION2017 每道题的名字,但是他通过一些特殊手段得到了ION2017 的命名串,现在小A 有Q 次询问:每次给定ION2017 的命名串和ION2018 的命名串,求有几种题目的命名,使得这个名字一定满足命题委员会的规定,即是ION2018 的命名串的一个非空连续子串且一定不会和ION2017 的任何一道题目的名字相同。
由于一些特殊原因,所有询问给出的ION2017 的命名串都是某个串的连续子串,详细可见输入格式。
第一行一个字符串 S S S ,之后询问给出的ION2017 的命名串都是 S S S 的连续子串。 第二行一个正整数 Q Q Q,表示询问次数。 接下来 Q Q Q 行,每行有一个字符串 T T T 和两个正整数 l , r l,r l,r,表示询问如果ION2017 的 命名串是 S [ l . . r ] S [l..r] S[l..r],ION2018 的命名串是 T T T 的话,有几种命名方式一定满足规定。
输出 Q Q Q 行,第 i i i 行一个非负整数表示第i 个询问的答案。
scbamgepe
3
smape 2 7
sbape 3 8
sgepe 1 9
12
10
4
| 测试点 | $∣S∣\leq $ | Q ≤ Q\leq Q≤ | ∑ ∣ T ∣ ≤ \sum |T|\leq ∑∣T∣≤ | 其他限制 |
| :----: | :-------------: | :-----: | :-------------: | :-----------: |
| 1 | 200 200 200 | 200 200 200 | 40000 40000 40000 | ∣ T ∣ ≤ 200 |T|\leq 200 ∣T∣≤200 |
| 2 | 1000 1000 1000 | 200 200 200 | 40000 40000 40000 | ∣ T ∣ ≤ 200 |T|\leq 200 ∣T∣≤200 |
| 3 | 1000 1000 1000 | 200 200 200 | 40000 40000 40000 | ∣ T ∣ ≤ 200 |T|\leq 200 ∣T∣≤200 |
| 4 | 1000 1000 1000 | 200 200 200 | 5 × 1 0 5 5 \times 10^5 5×105 | 无 |
| 5 | 1000 1000 1000 | 200 200 200 | 5 × 1 0 5 5 \times 10^5 5×105 | 无 |
| 6 | 5 × 1 0 5 5 \times 10^5 5×105 | 1 1 1 | 5 × 1 0 5 5 \times 10^5 5×105 | 无 |
| 7 | 5 × 1 0 5 5 \times 10^5 5×105 | 1 1 1 | 5 × 1 0 5 5 \times 10^5 5×105 | 无 |
| 8 | 1 0 5 10^5 105 | 1 0 5 10^5 105 | 2 × 1 0 5 2 \times 10^5 2×105 | 无 |
| 9 | 1 0 5 10^5 105 | 1 0 5 10^5 105 | 2 × 1 0 5 2 \times 10^5 2×105 | 字符串随机 |
| 10 | 2 × 1 0 5 2 \times 10^5 2×105 | 1 0 5 10^5 105 | 4 × 1 0 5 4 \times 10^5 4×105 | 无 |
| 11 | 2 × 1 0 5 2 \times 10^5 2×105 | 1 0 5 10^5 105 | 4 × 1 0 5 4 \times 10^5 4×105 | 字符串随机 |
| 12 | 3 × 1 0 5 3 \times 10^5 3×105 | 1 0 5 10^5 105 | 6 × 1 0 5 6 \times 10^5 6×105 | 无 |
| 13 | 3 × 1 0 5 3 \times 10^5 3×105 | 1 0 5 10^5 105 | 6 × 1 0 5 6 \times 10^5 6×105 | 字符串随机 |
| 14 | 4 × 1 0 5 4 \times 10^5 4×105 | 1 0 5 10^5 105 | 8 × 1 0 5 8 \times 10^5 8×105 | 无 |
| 15 | 4 × 1 0 5 4 \times 10^5 4×105 | 1 0 5 10^5 105 | 8 × 1 0 5 8 \times 10^5 8×105 | 字符串随机 |
| 16 | 5 × 1 0 5 5 \times 10^5 5×105 | 1 0 5 10^5 105 | 1 0 6 10^6 106 | 无 |
| 17 | 5 × 1 0 5 5 \times 10^5 5×105 | 1 0 5 10^5 105 | 1 0 6 10^6 106 | 字符串随机 |
| 18 | 2 × 1 0 5 2 \times 10^5 2×105 | 1 0 5 10^5 105 | 1 0 6 10^6 106 | 无 |
| 19 | 3 × 1 0 5 3 \times 10^5 3×105 | 1 0 5 10^5 105 | 1 0 6 10^6 106 | 无 |
| 20 | 4 × 1 0 5 4 \times 10^5 4×105 | 1 0 5 10^5 105 | 1 0 6 10^6 106 | 无 |
| 21 | 5 × 1 0 5 5 \times 10^5 5×105 | 1 0 5 10^5 105 | 1 0 6 10^6 106 | 无 |
| 22 | 5 × 1 0 5 5 \times 10^5 5×105 | 1 0 5 10^5 105 | 1 0 6 10^6 106 | 无 |
| 23 | 5 × 1 0 5 5 \times 10^5 5×105 | 1 0 5 10^5 105 | 1 0 6 10^6 106 | 无 |
| 24 | 5 × 1 0 5 5 \times 10^5 5×105 | 1 0 5 10^5 105 | 1 0 6 10^6 106 | 无 |
| 25 | 5 × 1 0 5 5 \times 10^5 5×105 | 1 0 5 10^5 105 | 1 0 6 10^6 106 | 无 |
对于前17个测试点的所有询问有 l = 1 , r = ∣ S ∣ l = 1 , r = ∣ S ∣ l=1,r=|S|l=1,r=∣S∣ l=1,r=∣S∣l=1,r=∣S∣
对于所有数据,保证 1 ≤ l ≤ r ≤ ∣ S ∣ 1\leq l \leq r \leq |S| 1≤l≤r≤∣S∣, 1 ≤ ∣ T ∣ ≤ 5 × 1 0 5 1\leq |T|\leq 5 \times 10^5 1≤∣T∣≤5×105
首先我们考虑如何处理 68 68 68分的情况, 显然我们可以先对 S S S建立 S A M SAM SAM, 再对 T T T建立 S A M SAM SAM, 并同时在 S S S上试图匹配到一个最长的后缀 s s s, 其长度为 l i m i lim_i limi,并记录其第一次出现的位置(方便确定分裂出去的子串能匹配的长度, 因为此时长度为 l e n [ i ] len[i] len[i]的子串并不是一个前缀)为 f i r [ i ] fir[i] fir[i]。这样的话设 c n t cnt cnt为最终 T T T自动机的大小, 最终的答案就是 ∑ i = 2 c n t l e n [ i ] − m a x ( l e n [ p a r [ i ] ] , l i m [ f i r [ i ] ] ) \sum_{i=2}^{cnt}len[i]-max(len[par[i]],lim[fir[i]]) ∑i=2cntlen[i]−max(len[par[i]],lim[fir[i]])。
现在考虑加入 [ l , r ] [l,r] [l,r]这个限制。 我们发现唯一的改变就是我们在 S S S的 S A M SAM SAM上跳的时候这个后缀 s s s不一定在 [ l , r ] [l,r] [l,r]中出现过, 所以我们用可持久化线段树维护 S S S的 e n d p o s endpos endpos集合, 按拓扑序从下往上合并, 在确定 l i m I lim_I limI的时候不断跳 p a r e n t parent parent链来保证这个要求即可。
总复杂度 O ( ∣ S ∣ + ∑ ∣ T ∣ + ( ( ∑ ∣ T ∣ ) + ∣ S ∣ ) l o g ( ∣ S ∣ ) ) O(|S|+\sum|T|+((\sum|T|)+|S|)log(|S|)) O(∣S∣+∑∣T∣+((∑∣T∣)+∣S∣)log(∣S∣))。
代码如下:
#include
#include
#include
#include
#include
#include
#define R register
#define IN inline
#define W while
#define gc getchar()
#define ll long long
#define MX 1000500
template <class T>
IN void in(T &x)
{
x = 0; R char c = gc;
for (; !isdigit(c); c = gc);
for (; isdigit(c); c = gc)
x = (x << 1) + (x << 3) + c - 48;
}
int L;
int root[MX], lim[MX], son[MX * 20][2];
char buf[MX];
namespace SGT
{
int arr;
#define ls son[now][0]
#define rs son[now][1]
void insert(int &now, R int lef, R int rig, R int tar)
{
if(!now) now = ++arr;
if(lef == rig) return;
int mid = lef + rig >> 1;
if(tar <= mid) insert(ls, lef, mid, tar);
else insert(rs, mid + 1, rig, tar);
}
int merge(R int x, R int y)
{
if(!x || !y) return x + y;
int now = ++arr;
ls = merge(son[x][0], son[y][0]);
rs = merge(son[x][1], son[y][1]);
return now;
}
bool query(R int now, R int lef, R int rig, R int lb, R int rb)
{
if(!now || lb > rb) return false;
if(lb <= lef && rig <= rb) return true;
int mid = lef + rig >> 1;
if(lb <= mid) if(query(ls, lef, mid, lb, rb)) return true;
if(rb > mid) if(query(rs, mid + 1, rig, lb, rb)) return true;
return false;
}
#undef ls
#undef rs
}
namespace SAM
{
int to[MX][26], par[MX], len[MX], siz[MX], buc[MX], ord[MX];
int cnt = 1, last = 1, cur, l;
IN void insert(R int id)
{
R int now = last, tar;
cur = ++cnt; len[cur] = len[last] + 1, siz[last = cur] = 1;
for (; now && !to[now][id]; now = par[now]) to[now][id] = cur;
if (!now) return par[cur] = 1, void();
tar = to[now][id];
if(len[tar] == len[now] + 1) return par[cur] = tar, void();
int nw = ++cnt; len[nw] = len[now] + 1;
par[nw] = par[tar], par[tar] = par[cur] = nw;
std::memcpy(to[nw], to[tar], sizeof(to[nw]));
for (; now && to[now][id] == tar; now = par[now]) to[now][id] = nw;
}
IN void build_SGT()
{
R int now;
for (R int i = 1; i <= cnt; ++i) buc[len[i]]++;
for (R int i = 1; i <= L; ++i) buc[i] += buc[i - 1];
for (R int i = cnt; i > 1; --i) ord[buc[len[i]]--] = i;
for (R int i = cnt; i > 1; --i)
{
now = ord[i];
if(siz[now]) SGT::insert(root[now], 1, L, len[now]);
root[par[now]] = SGT::merge(root[par[now]], root[now]);
}
}
}
namespace Solve
{
int cnt, last, cur, l;
int to[MX][26], par[MX], len[MX], fir[MX];
IN void reset()
{
for (R int i = 1; i <= cnt; ++i)
std::memset(to[i], 0, sizeof(to[i])), par[i] = 0;
last = cnt = 1;
}
IN void insert(R int id)
{
R int now = last, tar;
cur = ++cnt; len[cur] = len[last] + 1, fir[cur] = len[last = cur];
for (; now && !to[now][id]; now = par[now]) to[now][id] = cur;
if(!now) return par[cur] = 1, void();
tar = to[now][id];
if(len[tar] == len[now] + 1) return par[cur] = tar, void();
int nw = ++cnt; len[nw] = len[now] + 1, fir[nw] = fir[tar];
par[nw] = par[tar], par[tar] = par[cur] = nw;
std::memcpy(to[nw], to[tar], sizeof(to[nw]));
for(; now && to[now][id] == tar; now = par[now]) to[now][id] = nw;
}
IN ll calc(R int lef, R int rig)
{
ll ans = 0;
reset(); l = std::strlen(buf + 1);
R int now = 1, ln = 0, id;
for (R int i = 1; i <= l; ++i)
{
id = buf[i] - 'a';
insert(id);
W (233)
{
if(SAM::to[now][id] && SGT::query(root[SAM::to[now][id]], 1, L, lef + ln, rig))//endpos 应该加上len 才是正确的位置集合
{
++ln;
now = SAM::to[now][id];
break;
}
if(!ln) break;
--ln;
if(ln == SAM::len[SAM::par[now]]) now = SAM::par[now];
}
lim[i] = ln;
}
for (R int i = 2; i <= cnt; ++i)
ans += std::max(0, len[i] - std::max(len[par[i]], lim[fir[i]]));
return ans;
}
}
int main(void)
{
scanf("%s", buf + 1);
L = std::strlen(buf + 1);
for (R int i = 1; i <= L; ++i)
SAM::insert(buf[i] - 'a');
SAM::build_SGT();
int T, l, r;
in(T);
W (T--)
{
scanf("%s", buf + 1);
in(l), in(r);
printf("%lld\n", Solve::calc(l, r));
}
}