题意: 给出一个长度为 n n 的主串,接下来输入 m m 次询问,每次输入一个 Si S i (∑iSi<=1e5) ( ∑ i S i <= 1 e 5 ) ,询问在主串中 Si S i 不出现重叠的前提下最多出现了多少次。
做法: 因为太菜了不会AC自动机,就写了一发后缀自动机,然后wa了一下午,心态爆炸。晚上找错误的时候把空间开成了2倍,A了… 气的受不了,那么过分一定要写在博客上。
考虑后缀自动机,一般情况下后缀自动机处理可重复串比较多,遇到了不能重复的顿时就懵了。假如我们能维护出对于每一个自动机上的节点的
right
r
i
g
h
t
集合,那么对于每一次询问就可以自己从对应自动机上的
right
r
i
g
h
t
集一直二分跳着查找就行了。假如可以处理出来
right
r
i
g
h
t
集这样好像还是不行,例如主串1e5个a,1e5个询问每次都一个a,毫无疑问复杂度会爆炸。那么考虑离线处理询问,我们对于每一个询问在自动机上找到对应终止节点,标记上需要该节点需要询问的字符串长度(即每两个
right
r
i
g
h
t
之间的最小间隔),当然对于一个节点可能需要处理的询问有多个,并且可能重复,于是我对每个节点都开了
set
s
e
t
维护。这样总复杂度最差应该也不会达到
(n+n/2+n/3+....)
(
n
+
n
/
2
+
n
/
3
+
.
.
.
.
)
(
(
约为
)
)
那么对于每一个节点维护集最差情况下好像也会 n2 n 2 ,那么我们拓扑排序后启发式合并(就是把小集合塞大集合)因为对于处理每一个节点时他的后面节点的信息就没必要存储了,空间复杂度也是绝对ok的。
因为也是第一次用启发式合并
right
r
i
g
h
t
集,顺利过了当涨姿势记一下。
代码:
#include <bits/stdc++.h>
using namespace std;
const int Max = 2e5+5;
struct sam{
int pa[Max<<1], son[Max<<1][27], deep[Max<<1], cnt, root, last;//自动机相关
set<int> R[Max<<1];//用来维护每个节点的right集
inline int Newnode(int _deep){deep[++cnt]=_deep;return cnt;}
inline void pre(){root=last=Newnode(0);}
inline void SAM(int alp){
int np=Newnode(deep[last]+1);
int u=last;//R[np].insert(deep[np]);这样对于集合赋予初值也是正确的
memset(son[np], 0, sizeof(son[np]));
while(u&&!son[u][alp])son[u][alp]=np, u=pa[u];
if(!u)pa[np]=root;
else {
int v=son[u][alp];
if(deep[v]==deep[u]+1)pa[np]=v;
else {
int nv=Newnode(deep[u]+1);
memcpy(son[nv], son[v], sizeof(son[v]));
pa[nv]=pa[v], pa[v]=pa[np]=nv;
while(u&&son[u][alp]==v)son[u][alp]=nv, u=pa[u];
}
}
last=np;
}
int sum[Max<<1],tp[Max<<1];//拓扑排序相关
inline void toposprt(){//拓扑排序
for(int a=1; a<=deep[last]; a++)sum[a]=0;
for(int a=1; a<=cnt; a++)sum[deep[a]]++;
for(int a=1; a<=deep[last]; a++)sum[a]+=sum[a-1];
for(int a=1; a<=cnt; a++)tp[sum[deep[a]]--]=a;
}
void init(){//初始化,用的时候先调用才行
cnt=0;
memset(son[1], 0, sizeof(son[1]));
pre();
}
}sam;
char str[Max<<1], pat[Max<<1];
struct node{
int p, x, ans;
}Q[Max<<1];//离线询问,p:节点编号,x:询问串长度
int idx[Max<<1];//启发式合并相关
set<int> nes[Max<<1];//节点需要处理的串长度集合
vector<int> arr[Max<<1], cnt[Max<<1];//最后再对询问在节点上查询答案
int main(){
//freopen("a.txt", "r", stdin);
int n, m; sam.init();
scanf("%d%d", &n, &m);
scanf("%s", str);
int len=strlen(str);
for(int i=0; i<len; i++)sam.SAM(str[i]-'a');
for(int i=0, p=1; i<len; i++){
int u=str[i]-'a';
p=sam.son[p][u];
sam.R[p].insert(i+1);
}
for(int i=0; i<Max; i++)idx[i]=i;
sam.toposprt();
for(int i=0; i<m; i++)Q[i].ans=-1;
for(int i=0; i<m; i++){//离线处理询问
scanf("%s", pat);
len=strlen(pat);
int p=1;
for(int j=0; j<len; j++){
int u=pat[j]-'a';
if(!sam.son[p][u]){
Q[i].ans=0;break;
}
p=sam.son[p][u];
}
Q[i].p=p, Q[i].x=len;
}
for(int i=0; i<m; i++){
if(Q[i].ans==0)continue;
nes[Q[i].p].insert(Q[i].x);
}
for(int i=1; i<=sam.cnt; i++){
for(auto x : nes[i]){
arr[i].push_back(x);
}
}
for(int i=sam.cnt; i>0; i--){//处理过程
for(auto x : arr[sam.tp[i]]){//处理当前节点的询问
if(sam.R[idx[sam.tp[i]]].size()==0){
cnt[sam.tp[i]].push_back(0);
continue;
}
int sum=0;
auto tmp = sam.R[idx[sam.tp[i]]].begin();
while(tmp!=sam.R[idx[sam.tp[i]]].end()){
sum++; int kk=(*tmp)+ x;
tmp = sam.R[idx[sam.tp[i]]].lower_bound(kk);
}
cnt[sam.tp[i]].push_back(sum);
}
if(sam.R[idx[sam.tp[i]]].size()<sam.R[idx[sam.pa[sam.tp[i]]]].size()){//启发式合并更新父亲节点
for(auto x :sam.R[idx[sam.tp[i]]]){
sam.R[idx[sam.pa[sam.tp[i]]]].insert(x);
}
sam.R[idx[sam.tp[i]]].clear();
}
else {
for(auto x :sam.R[idx[sam.pa[sam.tp[i]]]]){
sam.R[idx[sam.tp[i]]].insert(x);
}
sam.R[idx[sam.pa[sam.tp[i]]]].clear();
idx[sam.pa[sam.tp[i]]]=idx[sam.tp[i]];
}
}
for(int i=0; i<m; i++){
if(Q[i].ans==0){
printf("0\n");
continue;
}
//每一次都去节点上查询答案
int idx=lower_bound(arr[Q[i].p].begin(), arr[Q[i].p].end(), Q[i].x)-arr[Q[i].p].begin();
printf("%d\n", cnt[Q[i].p][idx]);
}
return 0;
}