- 题意:
0 s v:添加价值为v的字符串s
1 t:查询t中含的s的权值和。(不停位置算多次)
- 思路:
在线AC自动机。
同学用过一个妙妙子的分块算法。
这里用二进制分组:通常用作把在线数据结构问题转离线
即当前有n个串。然后按n的二进制分成(\(<=log_2n\))个AC自动机分别维护答案。
e.g \(n=7(111)_2\)
此时会有三个AC自动机,分别表示串[1,4],[5,6],[7,7]个数(sz[])分别为\(2^2,2^1,2^0\)
查询也so easy.就每局每个自动机,在上面查找。最多查\(log_2n\)次。
修改,每次加入一个串,先建一个只包含它本身sz[]=1的自动机。
这里我们用stack维护每个自动机信息。
然后如果最后一个自动机与前一个sz相等。把后两个自动机合并。
因为每个串最多被合并\(log_2(s的个数m)\)次。然后复杂度是\(O(\sum|S|log_2(m)26)\)
- code:
细节mx_nd[i]表示第i个自动机的最大(最近添加)节点(因为节点是连续加入的)。
每次删除st[tp]时,把节点数nd也回溯到mx_nd[tp-2](过程中清空删的节点),然后再合并st[tp-1]与st[tp]
这样的好处是,空间是线性的。而且这么写常数也很小。
#include
using namespace std;
const int N=1e6+5;
typedef long long ll;
int va[N],sz[N],tp,rt[N],fail[N],go[N][27],nd,len,head[N],L[N],stot,mx_nd[N];
ll val[N],lans;
int s[N];
char ch[N];
void Insert(int u,int l,int r,int w) {
for(int i=l;i<=r;i++) {
int x=s[i];
if(!go[u][x])go[u][x]=++nd;
u=go[u][x];
}
val[u]+=w;
}
int Q[N],hd,tl;
void gt_fail(int root) {
// printf("!%d\n",root);
hd=1,tl=0;
for(int i=0;i<26;i++) {
if(go[root][i]) fail[go[root][i]]=root,Q[++tl]=go[root][i];
else go[root][i]=root;
}
while(hd<=tl) {
int u=Q[hd++];val[u]+=val[fail[u]];
for(int i=0;i<26;i++) {
if(go[u][i]) fail[go[u][i]]=go[fail[u]][i],Q[++tl]=go[u][i];
else go[u][i]=go[fail[u]][i];
}
}
}
void _Pass() {
while(tp>1&&sz[tp]==sz[tp-1]) {
int tmp=nd;nd=mx_nd[tp-2];
while(tmp>nd) {val[tmp]=fail[tmp]=0;for(int i=0;i<26;i++)go[tmp][i]=0;tmp--;}
tp--;sz[tp]*=2;rt[tp]=++nd;
for(int i=stot;i>=L[tp];i--) Insert(rt[tp],head[i],i==stot?len:head[i+1]-1,va[i]);
gt_fail(rt[tp]);mx_nd[tp]=nd;
}
}
void Query() {
int ln=strlen(ch);
lans=0;
for(int i=1;i<=tp;i++) {
int u=rt[i];
for(int j=0;j