【洛谷7582】「RdOI R2」風雨(rain)(分塊+AC自動機)
阿新 • • 發佈:2021-07-13
給定$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$。
,\(f\)記錄清零標記(\(f=0\)表示清零),\(g\)記錄加法標記。
- 給定\(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\)
於是,對於一次詢問,我們只要求出\(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; }