题解--风雨
abruce
2021-04-06 08:42:46
感觉 AC 自动机可以出很毒瘤的数据结构题啊,可是为什么没人出啊。/kk
难度和码量大致相当于一个比较简单的 Ynoi 吧。
### 20pts
直接暴力维护 $a_i$,暴力 KMP 即可。
### 40pts
线段树上每个节点开一个 AC 自动机,然后统计出现次数就可以了,时间复杂度 $O(nlogn)\sim O(nlog^2n)$。
### 60pts
似乎可以用 ODT 维护权值,然后将问题转化为区间内字符串在大串出现次数,线段树即可。注意上一个部分分不能用这种方法。
## 100pts
前置知识:[e-Government](https://www.luogu.com.cn/problem/CF163E)
首先肯定是要建 AC 自动机的。区间加,区间推平,线段树?可是这个怎么 pushup 啊。我们想到另一种支持区间加,区间推平的数据结构——分块。让我们看分块怎么支持以上的操作。
首先,我们每 $\sqrt{n}$ 个串建一个 AC 自动机,同时建出它的 fail 树。对于初始的 $a_i$ 我们像 e-Government 那样用一个树状数组进行子树加,单点查询。这样,我们在边角修改的时候也可以对树状数组进行操作。
对于整块的修改,我们需要多记录一个 $tag$ 数组和一个 $dlt$ 数组。区间整块推平时,我们将 $dlt$ 数组修改为那个值,同时将 $tag$ 置为 $1$,代表这个区间已经被推平。区间加的时候,我们只需要对 $dlt$ 进行操作即可。
在查询的时候,我们对于边角,直接暴力 KMP 即可。对于整块,我们分成两种情况产生贡献。
如果这个区间 $tag$ 为 $1$,我们只需要查询这个块的所有串的出现次数和乘上 $dlt$ 即可。如果 $tag$ 为 $0$,则不仅需要查询这个块的所有串的出现次数和乘上 $dlt$,还需要在树状数组中进行查询。
设字符串总长度为 $L$,则总时间复杂度 $O(L\sqrt{n}\log L)$,代码细节比较多,有点长,写的时候注意。
```cpp
#include<bits/stdc++.h>
#define addtag(i,k) a[i]+=k,add(l[bj[i]],k),add(r[bj[i]]+1,-k)//给 i 的贡献加上 k
#define chtag(i,k) add(l[bj[i]],k-a[i]),add(r[bj[i]]+1,a[i]-k),a[i]=k//将 i 的贡献改为 k
using namespace std;
typedef long long ll;
inline int read() {
int __x=0,__f=1;
char __c=getchar();
while(__c<'0'||__c>'9') {
if(__c=='-')__f=-1;
__c=getchar();
}
while(__c>='0'&&__c<='9') {
__x=__x*10+__c-'0';
__c=getchar();
}
return __x*__f;
}
inline void write(ll __x) {
if(__x<0) {
putchar('-');
__x=-__x;
}
if(__x>=10)write(__x/10);
putchar(__x%10+'0');
}
inline string readstr() {
char __ch=getchar();
string __st1="";
while (__ch<'0'||__ch>'z')
__ch=getchar();
while (__ch>='0'&&__ch<='z')
__st1+=__ch,__ch=getchar();
return __st1;
}
const int maxn=2e5+5,mod=1e9+7;
struct edge {
int next,to;
} e[maxn*2];
int t[maxn][3],fail[maxn],h[maxn],v[maxn],dlt[maxn],bj[maxn],l[maxn],r[maxn],bel[maxn],n,m,cnt,tot,pos,rt[maxn],lb[maxn],rb[maxn],nxt[maxn],sqrtn,sn;
ll c[maxn],a[maxn],tag[maxn];
string s[maxn];
queue<int> q;
void addedge(int x,int y) {
e[++cnt].next=h[x];
e[cnt].to=y;
h[x]=cnt;
}
void insert(const string &s,int rot,int u) {
int root=rot,len=s.length(),y;//插入以 rot 为根的 Trie 中
for(register int i=0; i<len; i++) {
y=s[i]-'a';
if(!t[root][y]) {
t[root][y]=++tot;
}
root=t[root][y];
}
v[root]++;
bj[u]=root;
}
void getfail(int rot) {
for(register int i=0; i<3; i++) {
t[0][i]=rot;
}
q.push(rot);
while(!q.empty()) {
int u=q.front(),f=fail[u];
q.pop();
v[u]+=v[f];
for(register int i=0; i<3; i++) {
int j=t[u][i];
if(!j) {
t[u][i]=t[f][i];
continue;
}
fail[j]=t[f][i];
q.push(j);
}
}
}//构建 Trie 图
void dfs(int u) {
l[u]=++pos;
for(register int i=h[u]; i; i=e[i].next) {
int j=e[i].to;
dfs(j);
}
r[u]=pos;
}
void add(int x,int v) {
while(x<=pos)c[x]+=v,x+=x&(-x);
}
ll ask(int x) {
ll sum=0;
while(x)sum+=c[x],x-=x&(-x);
return sum;//答案需要开 long long
}
void clrtag(int w) {
int v;
for(register int i=lb[w]; i<=rb[w]; i++)v=0,tag[w]?v=dlt[w]:v=a[i]+dlt[w],chtag(i,v);//将这个区间的 dlt 清空,以便维护出 a_i 的真实值
dlt[w]=tag[w]=0;
}
void add(int lx,int ry,int k) {
int x=bel[lx],y=bel[ry],v;
if(x==y) {
if(tag[x])clrtag(x);
for(register int i=lx; i<=ry; i++)addtag(i,k);
return;
}
if(tag[x])clrtag(x);
for(register int i=lx; i<=rb[x]; i++)addtag(i,k);
if(tag[y])clrtag(y);
for(register int i=lb[y]; i<=ry; i++)addtag(i,k);
for(register int i=x+1; i<=y-1; i++)dlt[i]+=k;
}
void change(int lx,int ry,int k) {
int x=bel[lx],y=bel[ry],v;
if(x==y) {
if(dlt[x])clrtag(x);
for(register int i=lx; i<=ry; i++)chtag(i,k);
return;
}
if(dlt[x])clrtag(x);
for(register int i=lx; i<=rb[x]; i++)chtag(i,k);
if(dlt[y])clrtag(y);
for(register int i=lb[y]; i<=ry; i++)chtag(i,k);
for(register int i=x+1; i<=y-1; i++)dlt[i]=k,tag[i]=1;
}
ll getkmp(string s,string t) {
s=' '+s,t=' '+t;
ll sum=0;
nxt[1]=0;
int n=s.length()-1,m=t.length()-1;
for(register int i=2,j=0; i<=n; i++) {
while(j>0&&s[i]!=s[j+1]) {
j=nxt[j];
}
if(s[i]==s[j+1])j++;
nxt[i]=j;
}
for(register int i=1,j=0; i<=m; i++) {
while(j>0&&(j==n||t[i]!=s[j+1]))j=nxt[j];
if(t[i]==s[j+1])j++;
sum+=j==n;
}
return sum;
}
ll query(int lx,int ry,const string &st) {
int x=bel[lx],y=bel[ry];
ll ans=0;
if(x==y) {
for(register int i=lx; i<=ry; i++)if(s[i].length()<=st.length())ans+=getkmp(s[i],st)*(tag[x]?dlt[x]:a[i]+dlt[x]);
return ans;
}
for(register int i=lx; i<=rb[x]; i++)if(s[i].length()<=st.length())ans+=getkmp(s[i],st)*(tag[x]?dlt[x]:a[i]+dlt[x]);
for(register int i=lb[y]; i<=ry; i++)if(s[i].length()<=st.length())ans+=getkmp(s[i],st)*(tag[y]?dlt[y]:a[i]+dlt[y]);//注意此处必须先判谁的长度更长,否则复杂度会退化
for(register int i=x+1; i<=y-1; i++) {
int u=rt[i],len=st.length();
for(register int j=0; j<len; j++) {
u=t[u][st[j]-'a'];
ans+=dlt[i]*v[u]+(!tag[i])*ask(l[u]);//在没有 tag 的情况下才在树状数组查询
}
}
return ans;
}
int main() {
int op,x,y,z;
string st;
n=read(),m=read();
sqrtn=int(sqrt(n));
sn=n/sqrtn+(n%sqrtn!=0);
for(register int i=1; i<=sn; i++)lb[i]=(i-1)*sqrtn+1,rb[i]=min(i*sqrtn,n),rt[i]=++tot;
for(register int i=1; i<=n; i++)bel[i]=(i-1)/sqrtn+1;
for(register int i=1; i<=n; i++)s[i]=readstr(),a[i]=read(),insert(s[i],rt[bel[i]],i);
for(register int i=1; i<=sn; i++)getfail(rt[i]);
for(register int i=1; i<=tot; i++)addedge(fail[i],i);
for(register int i=1; i<=sn; i++)dfs(rt[i]);
for(register int i=1; i<=n; i++)add(l[bj[i]],a[i]),add(r[bj[i]]+1,-a[i]);//初始需要插入a_i
while(m--) {
op=read(),x=read(),y=read();
switch(op) {
case 1: {
z=read(),add(x,y,z);
break;
}
case 2: {
z=read(),change(x,y,z);
break;
}
case 3: {
st=readstr(),write(query(x,y,st)),putchar('\n');
break;
}
}
}
return 0;
}
```