题目
思路
不难想到,把询问按照
r
r
r 排序。同样不难发现,这样没什么用。
用一种接地气的方法:先暴力,再优化。虽然一般只有看过题解之后才知道这么推。
暴力:询问全串
先假设 l = 1 , r = n l=1,r=n l=1,r=n 。考虑对 S , T S,T S,T 都建立一个后缀自动机。为啥对 T T T 建立后缀自动机呢?因为我们要对 T T T 的 子串去重。为啥对 S S S 建立后缀自动机呢?因为我们要 跑字符串匹配。
我们试着用 S S S 的每个子串去筛掉不合法的 T T T 的子串。直接把 T T T 在 S S S 上做字符串匹配,就会得到一个匹配长度 p i p_i pi ,意义是,在 T T T 长度为 i i i 的前缀上选一个后缀,长度必须大于 p i p_i pi 。然后,根据我们前面所说的,只这样搞有点问题。我们把 p i p_i pi 打到 T T T 的 S A M \tt SAM SAM 上,取个 max \max max 。
最后,我们重新扫一次 T T T 的后缀自动机,将这个值转移给父节点,然后计算答案。
变强:任意询问
想办法优化上面的这个做法。
复杂度瓶颈在哪里?在于每次要重新求一个 S [ l , r ] S[l,r] S[l,r] 的 S A M \tt SAM SAM 。其目的是什么?求 p i p_i pi 。
如果我对全串 S S S 建自动机,还是能求出 p i p_i pi ,问题就已经解决了!
后缀自动机可持久化怎么样?搞点现实的。比如,根据一些信息忽略节点,使得没被忽略的节点与
S
[
l
,
r
]
S[l,r]
S[l,r] 直接建立
S
A
M
\tt SAM
SAM 等效。
求出每个节点的 e n d p o s endpos endpos(字符串结束位置的集合)。那么,对于长度为 l e n len len 的字符串,只要 ∃ x ∈ e n d p o s , l + l e n − 1 ≤ x ≤ r \exist x\in endpos,\;l+len-1\le x\le r ∃x∈endpos,l+len−1≤x≤r 就行了!这个条件看上去挺好判断的。
不仅如此,每个节点的 l e n len len 也会改变。它本来的长度是 x x x ,但是因为有了出现位置的限制,它可能变成 max y ∈ e n d p o s y ≤ r ( y − l + 1 ) \max_{y\in endpos}^{y\le r}(y-l+1) maxy∈endposy≤r(y−l+1) 。两个值取较小即可。
最后一个问题:怎么求 e n d p o s endpos endpos 集合?当然啦,也可以不求出来,只要能进行上面两个判断就行。
不难发现,二者都只需要求出 e n d p o s endpos endpos 集合中不超过 r r r 的最大 x x x 。这东西可以用 权值线段树 来做。为什么会想到这种东西呢?因为它支持 可持久化合并。毕竟求 e n d p o s endpos endpos 这种东西,众所周知,是直接从子节点转移过来的。复杂度 O ( n log n ) \mathcal O(n\log n) O(nlogn) 就做完了。
另:对 r r r 排序后,用 L C T \tt LCT LCT 维护后缀树也可以方便的获得 e n d p o s endpos endpos 。
有人写这种做法吗?
代码
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int MaxN = 2000005;
const int CharSiz = 26;
int n; // to let SgTree know right point
namespace SgTree{
int son[MaxN*30][2], mx[MaxN*30];
int totNode; // dynamic new node
void pushUp(int o){
if(son[o][1] == 0)
mx[o] = mx[son[o][0]];
else mx[o] = mx[son[o][1]];
}
int merge(int a,int b){
if(!a || !b) return a+b;
int x = ++ totNode; // lasting
son[x][0] = merge(son[a][0],son[b][0]);
son[x][1] = merge(son[a][1],son[b][1]);
pushUp(x); return x;
}
int query(int qr,int o,int l=1,int r=n){
if(!o) return 0; // empty set
if(r <= qr) return mx[o]; // ql = 1
int m = (l+r)>>1; // split line
if(qr <= m) return query(qr,son[o][0],l,m);
return max(query(qr,son[o][0],l,m),
query(qr,son[o][1],m+1,r));
}
void modify(int qid,int &o,int l=1,int r=n){
if(!o) o = ++ totNode; // must
if(l == r) return void(mx[o] = l);
int m = (l+r)>>1; // split line
if(qid <= m) modify(qid,son[o][0],l,m);
else modify(qid,son[o][1],m+1,r);
pushUp(o); // dirrectly change it
}
}
// helper of Bucket Sort
int item[MaxN], bucket[MaxN];
int rt[MaxN]; // for SgTree
struct SAM{
int ch[MaxN][CharSiz];
int fa[MaxN], len[MaxN];
int cntNode, lst, ep[MaxN];
SAM(){ cntNode = 0; }
void clear(){
memset(fa+1,0,cntNode<<2);
for(int i=1; i<=cntNode; ++i)
memset(ch[i],0,CharSiz<<2);
memset(die+1,0,cntNode<<2);
memset(ep+1,0,cntNode<<2);
cntNode = lst = 1;
}
void add(char c){
int p = lst; lst = ++ cntNode;
int n = lst; len[n] = len[p]+1;
ep[n] = len[n]; // endpos
for(; p&&!ch[p][c]; p=fa[p])
ch[p][c] = n; // link
if(!p) return void(fa[n] = 1);
int q = ch[p][c]; // maybe
if(len[q] == len[p]+1)
return void(fa[n] = q);
int nq = ++ cntNode;
memcpy(ch[nq],ch[q],CharSiz<<2);
fa[nq] = fa[q]; // copy
len[nq] = len[p]+1;
fa[n] = fa[q] = nq;
for(; p&&ch[p][c]==q; p=fa[p])
ch[p][c] = nq;
}
void Sort(){
memset(bucket+1,0,cntNode<<2);
for(int i=2; i<=cntNode; ++i)
++ bucket[len[i]]; // except 1
for(int i=2; i<=cntNode; ++i)
bucket[i] += bucket[i-1];
for(int i=2; i<=cntNode; ++i)
item[bucket[len[i]]--] = i;
}
/** @brief build SgTree */
void build(){
Sort(); // topo
for(int i=cntNode-1; i; --i){
int x = item[i]; // processing
if(ep[x]) // a prefix
SgTree::modify(ep[x],rt[x]);
rt[fa[x]] = SgTree::merge(rt[fa[x]],rt[x]);
}
}
int die[MaxN]; // tag of "p_i"
long long calc(){
Sort(); long long res = 0;
for(int i=cntNode-1; i; --i){
int x = item[i]; // processing
die[x] = min(die[x],len[x]);
res += len[x]-max(len[fa[x]],die[x]);
die[fa[x]] = max(die[fa[x]],die[x]);
}
return res;
}
};
SAM S, T; // two SAMs
char str[MaxN]; // original str(T)
void match(int len,int L,int R){
T.clear(); // many asks
int s = 1, t = 1; // node on SAM
int lens = 0; // how long do we match
for(int i=0; i<len; ++i){
char c = str[i]-'a';
T.add(c); // construct meanwhile
for(; s; s=S.fa[s],lens=S.len[s]){
int nxt = S.ch[s][c];
if(!nxt) continue;
// printf("nxt = %d (len = %d, ep = %d)\n",nxt,S.len[nxt],S.ep[nxt]);
int ep = SgTree::query(R,rt[nxt]);
// printf("ep = %d\n",ep);
ep = ep-L+1; // longest
if(S.len[S.fa[nxt]]+1 <= ep){
lens = min(lens+1,ep);
s = nxt; break;
}
}
if(!s){ s = 1; lens = 0; }
// printf("lens = %d\n",lens);
for(; t&&!T.ch[t][c]; t=T.fa[t]);
for(t=T.ch[t][c]; t; t=T.fa[t])
if(T.len[T.fa[t]]+1 <= lens)
break; // just stop here
if(!t) t = 1; // empty
T.die[t] = max(T.die[t],lens);
}
printf("%lld\n",T.calc());
}
int main(){
S.clear(); // or "cntNode" is bad
scanf("%s",str), n = strlen(str);
for(int i=0; i<n; ++i)
S.add(str[i]-'a');
S.build(); int q = readint();
for(int L,R; q; --q){
scanf("%s",str);
L = readint(), R = readint();
match(strlen(str),L,R);
}
return 0;
}