【洛谷7582】「RdOI R2」风雨(rain)(分块+AC自动机)
- 给定\(n\)个字符串\(s_{1\sim i}\)以及相应的权值\(a_{1\sim i}\)。
- \(m\)次操作,分为三种:给\(a_{l\sim r}\)加上\(k\);把\(a_{l\sim r}\)赋值为\(k\);给定一个字符串\(S\),假设\(s_i\)在\(S\)中出现\(ct_i\)次,求\(\sum_{i=l}^rct_i\times a_i\)。
- \(n,m\le3\times10^4,\sum|s_i|,\sum|S|\le2\times10^5\)
分块处理序列修改
考虑我们用分块来处理序列修改。
方便起见把赋值操作看成区间清零+区间加法。
对于每个块记录两个变量\(f,g\),\(f\)记录清零标记(\(f=0\)表示清零),\(g\)记录加法标记。
于是,对于一次询问,我们只要求出\(v=\sum_{i=l}^rct_i\times a_i,c=\sum_{i=l}^rct_i\),则答案就是\(f\times v+g\times c\)。
注意这题卡空间,因此我们采用分块的离线套路,即枚举每一个块分别考虑每一个询问,就不用对于每个块开一份数组了。
\(AC\)自动机
我们对于当前块的所有字符串建出\(AC\)自动机。
然后开两个树状数组分别用于求\(v,c\),对于每个关键点在转化成\(dfs\)序列后给\(fail\)树上子树内节点打标记。
如果是散块询问,我们另开一个树状数组,枚举询问区间内每个关键点给子树打上标记然后询问。
代码:\(O(S\sqrt nlogS)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 30000
#define SZ 200000
#define LL long long
using namespace std;
int n,m,sz,a[N+5],id[N+5],op[N+5],ql[N+5],qr[N+5],qv[N+5];string st[N+5],qs[N+5];
namespace AC//AC自动机
{
int Nt;struct node {int F,S[4];}O[SZ+5];I int Ins(Cn string& s)//插入字符串
{
RI x=1;for(RI i=0,l=s.length(),t;i^l;++i) !O[x].S[t=s[i]&31]&&(O[x].S[t]=++Nt),x=O[x].S[t];return x;
}
int d,dI[SZ+5],dO[SZ+5],ee,lnk[SZ+5];struct edge {int to,nxt;}e[SZ+5];I void dfs(CI x)//遍历fail树
{
dI[x]=++d;for(RI i=lnk[x];i;i=e[i].nxt) dfs(e[i].to);dO[x]=d;
}
int q[SZ+5];I void Build()//建AC自动机
{
RI i,k,H=1,T=0;for(i=1;i<=3;++i) (O[1].S[i]?O[q[++T]=O[1].S[i]].F:O[1].S[i])=1;
W(H<=T) for(k=q[H++],i=1;i<=3;++i) (O[k].S[i]?O[q[++T]=O[k].S[i]].F:O[k].S[i])=O[O[k].F].S[i];
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
for(i=2;i<=Nt;++i) add(O[i].F,i);dfs(1);
}
I void Cl() {W(Nt) lnk[Nt]=O[Nt].S[1]=O[Nt].S[2]=O[Nt].S[3]=0,--Nt;Nt=1,ee=d=0;}//清空
}
struct TreeArray
{
LL a[SZ+5];I void Cl() {for(RI i=1;i<=AC::Nt;++i) a[i]=0;}//清空
I void A(RI x,Cn LL& v) {W(x<=AC::Nt) a[x]+=v,x+=x&-x;}//修改
I LL Q(RI x,LL t=0) {W(x) t+=a[x],x-=x&-x;return t;}//询问
I void U(CI i,Cn LL& v) {A(AC::dI[id[i]],v),A(AC::dO[id[i]]+1,-v);}//修改AC自动机fail树上i子树内的节点
}V,C,V_;
LL ans[N+5];I void Solve(CI L,CI R)//处理[L,R]这个块
{
RI i;for(AC::Cl(),i=L;i<=R;++i) id[i]=AC::Ins(st[i]);//记录每个字符串对应节点编号
for(AC::Build(),C.Cl(),V.Cl(),i=L;i<=R;++i) V.U(i,a[i]),C.U(i,1);
RI j,x;LL f=1,g=0,c,v;for(i=1;i<=m;++i) switch(op[i])
{
#define PD() for(j=L;j<=R;++j) V.U(j,f*a[j]+g-a[j]),a[j]=f*a[j]+g;f=1,g=0;
case 1:if(qr[i]<L||ql[i]>R) break;if(ql[i]<=L&&R<=qr[i]) {g+=qv[i];break;}PD();//无交;整块
for(j=max(ql[i],L);j<=min(qr[i],R);++j) V.U(j,qv[i]),a[j]+=qv[i];break;//散块
case 2:if(qr[i]<L||ql[i]>R) break;if(ql[i]<=L&&R<=qr[i]) {f=0,g=qv[i];break;}PD();//无交;整块
for(j=max(ql[i],L);j<=min(qr[i],R);++j) V.U(j,qv[i]-a[j]),a[j]=qv[i];break;//散块
case 3:if(qr[i]<L||ql[i]>R) break;//无交
if(ql[i]<=L&&R<=qr[i]) {for(j=c=v=0,x=1;j^qv[i];++j)//整块
x=AC::O[x].S[qs[i][j]&31],v+=V.Q(AC::dI[x]),c+=C.Q(AC::dI[x]);ans[i]+=f*v+g*c;break;}//分别求出权值和与出现次数和
PD();for(j=max(ql[i],L);j<=min(qr[i],R);++j) V_.U(j,a[j]);//枚举询问区间内字符串打标记
for(j=0,x=1;j^qv[i];++j) x=AC::O[x].S[qs[i][j]&31],ans[i]+=V_.Q(AC::dI[x]);//散块
for(j=max(ql[i],L);j<=min(qr[i],R);++j) V_.U(j,-a[j]);break;//清空临时树状数组
}
}
int main()
{
ios::sync_with_stdio(false);RI i;for(cin>>n>>m,i=1;i<=n;++i) cin>>st[i]>>a[i];
for(i=1;i<=m;++i) cin>>op[i]>>ql[i]>>qr[i],op[i]^3?(cin>>qv[i],0):(cin>>qs[i],qv[i]=qs[i].length());
for(sz=sqrt(n),i=1;i<=(n-1)/sz+1;++i) Solve((i-1)*sz+1,min(i*sz,n));//枚举每个块去考虑每一个询问
for(i=1;i<=m;++i) op[i]==3&&cout<<ans[i]<<endl;return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒