题解--风雨

· · 题解

感觉 AC 自动机可以出很毒瘤的数据结构题啊,可是为什么没人出啊。/kk
难度和码量大致相当于一个比较简单的 Ynoi 吧。

20pts

直接暴力维护 a_i,暴力 KMP 即可。

40pts

线段树上每个节点开一个 AC 自动机,然后统计出现次数就可以了,时间复杂度 O(nlogn)\sim O(nlog^2n)

60pts

似乎可以用 ODT 维护权值,然后将问题转化为区间内字符串在大串出现次数,线段树即可。注意上一个部分分不能用这种方法。

100pts

前置知识:e-Government
首先肯定是要建 AC 自动机的。区间加,区间推平,线段树?可是这个怎么 pushup 啊。我们想到另一种支持区间加,区间推平的数据结构——分块。让我们看分块怎么支持以上的操作。
首先,我们每 \sqrt{n} 个串建一个 AC 自动机,同时建出它的 fail 树。对于初始的 a_i 我们像 e-Government 那样用一个树状数组进行子树加,单点查询。这样,我们在边角修改的时候也可以对树状数组进行操作。
对于整块的修改,我们需要多记录一个 tag 数组和一个 dlt 数组。区间整块推平时,我们将 dlt 数组修改为那个值,同时将 tag 置为 1,代表这个区间已经被推平。区间加的时候,我们只需要对 dlt 进行操作即可。
在查询的时候,我们对于边角,直接暴力 KMP 即可。对于整块,我们分成两种情况产生贡献。
如果这个区间 tag1,我们只需要查询这个块的所有串的出现次数和乘上 dlt 即可。如果 tag0,则不仅需要查询这个块的所有串的出现次数和乘上 dlt,还需要在树状数组中进行查询。
设字符串总长度为 L,则总时间复杂度 O(L\sqrt{n}\log L),代码细节比较多,有点长,写的时候注意。

#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;
}