传送门
渐渐发现, 自己曾经认为的不可做题, 被一道一道地做了
尽管机房盛行装菜, 但无论是我, 还是机房的伙伴, 都在一天一天地变强
A了这道题, 可能也只有感动, 激动, 自豪能形容我的心情
解题思路 :
先考虑暴力
我最原始的暴力 ----
把 l -- r 之间的拿出来建SAM, 然后询问串的每一个后缀那上去跑, 终止位置记为 pos
那么 i -- pos 这些都是出现了的, 对答案的贡献就是 n - pos
考虑优化一下, 我们能不能把询问串拿上去只跑一次呢 ? 当然可以
我们记 len[i] 为 [1--i] 的后缀, 能在原串匹配的最长长度, 显然对答案的贡献就是 i - len[i]
怎么跑呢?
先考虑 l = 1, r = |S|的情况
如果下一位有这个字母, 那么len[i]++, 并且走到那个字母, 否则跳后缀链接, 并将 len[i] 更新
那么其他情况呢 ? 我开始想偏了, 想什么离线啊之类的 ...
其实往简单的想, 如果那个点的 endpos 集合一个在 L -- R 之间的都没有, 我们不走就是了
怎么记录 endpos 集合 ? 我们发现一个点的 endpos 集合就是它的子树的所有点的 pos
那么我们对 parent 树按dfs序建一个主席树, 就可以查询了
已经打算开始写了, 却又暴露了我思维不严谨的地方
询问串的一个子串可能出现多次, 难道每次都要算到答案里吗?
我们需要去重 ! 那么对于询问串再建一个SAM, 对于SAM上的每一个节点来考虑
还有一些细节, 比如说不是查 [L, R] 区间, 而要查[L + 当前以经匹配的len, R] 的区间
然后失配过后不能直接跳 link, 因为我们将 len 缩小过后有可能要查的串就出现了
思维不严谨啊 !
#include<bits/stdc++.h>
#define N 2000050
using namespace std;
typedef long long ll;
char S[N];
int T, n, c[N], A[N], p[N];
char s[N];
vector<int> v[N]; int st[N], ed[N], sign, rt[N];
struct Segmentree{
int ls[N * 15], rs[N * 15], sum[N * 15], tot;
#define mid ((l+r) >> 1)
void Build(int &x, int l, int r){
x = ++tot; if(l == r) return;
Build(ls[x], l, mid); Build(rs[x], mid+1, r);
}
void Insert(int &x, int last, int l, int r, int pos){
x = ++tot; ls[x] = ls[last]; rs[x] = rs[last]; sum[x] = sum[last] + 1;
if(l == r) return;
if(pos <= mid) Insert(ls[x], ls[last], l, mid, pos);
else Insert(rs[x], rs[last], mid+1, r, pos);
}
int Quary(int a, int b, int l, int r, int L, int R){
if(!a) return 0;
if(L<=l && r<=R) return sum[a] - sum[b];
int ans = 0;
if(L<=mid) ans += Quary(ls[a], ls[b], l, mid, L, R);
if(R>mid) ans += Quary(rs[a], rs[b], mid+1, r, L, R);
return ans;
}
}Seg;
struct SAM{
struct Node{ int ch[26], link, len, pos;} t[N];
int tot, last;
SAM(){ tot = last = 1;}
void clear(){
for(int i=1; i<=tot; i++){
for(int j=0; j<26; j++) t[i].ch[j] = 0;
t[i].link = t[i].len = t[i].pos = 0;
} tot = last = 1;
}
void Extend(int Id, int c){
int cur = ++tot, p = last;
t[cur].len = t[p].len + 1; t[cur].pos = Id;
for(;p && !t[p].ch[c]; p = t[p].link) t[p].ch[c] = cur;
if(!p) t[cur].link = 1;
else{
int q = t[p].ch[c];
if(t[q].len == t[p].len + 1) t[cur].link = q;
else{
int clone = ++tot; t[clone].pos = t[q].pos;
t[clone].link = t[q].link; t[clone].len = t[p].len + 1;
for(int i=0; i<26; i++) t[clone].ch[i] = t[q].ch[i];
for(;p && t[p].ch[c] == q; p = t[p].link) t[p].ch[c] = clone;
t[q].link = t[cur].link = clone;
}
} last = cur;
}
void dfs(int u){
st[u] = ++sign; rt[sign] = rt[sign - 1];
if(t[u].pos) Seg.Insert(rt[sign], rt[sign], 1, n, t[u].pos);
for(int i=0; i<v[u].size(); i++){
int t = v[u][i]; dfs(t);
} ed[u] = sign;
}
void Build(){
for(int i=2; i<=tot; i++) v[t[i].link].push_back(i);
Seg.Build(rt[0], 1, n); dfs(1);
}
void Find(int &u, int &len, int L, int R, int c){
while(1){
int son = t[u].ch[c];
if(t[u].ch[c] && Seg.Quary(rt[ed[son]], rt[st[son]-1], 1, n, L+len, R)){
len++; u = son; return;
}
if(!len) return;
len--; if(len < t[t[u].link].len + 1) u = t[u].link;
}
}
ll calc(){
ll ans = 0;
for(int i=2; i<=tot; i++){
ans += max(0, t[i].len - max(t[t[i].link].len, p[t[i].pos]));
} return ans;
}
}sam1, sam2;
int main(){
scanf("%s", S+1); n = strlen(S+1);
for(int i=1; i<=n; i++) sam1.Extend(i, S[i] - 'a');
sam1.Build();
scanf("%d", &T);
while(T--){
scanf("%s", s+1);
int len = strlen(s+1);
int l, r; scanf("%d%d", &l, &r);
sam2.clear();
int now = 1;
for(int i=1; i<=len; i++){
p[i] = p[i-1];
sam1.Find(now, p[i], l, r, s[i] - 'a');
}
for(int i=1; i<=len; i++) sam2.Extend(i, s[i] - 'a');
printf("%lld\n", sam2.calc());
} return 0;
}