1. 程式人生 > 其它 >【洛谷7582】「RdOI R2」風雨(rain)(分塊+AC自動機)

【洛谷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\)個字串\(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;
}
敗得義無反顧,弱得一無是處