zoukankan      html  css  js  c++  java
  • 【洛谷5287】[HNOI2019] JOJO(主席树优化KMP)

    点此看题面

    大致题意: 每次往一个字符串末尾加上(x)个字符(c),或者回到某一历史版本,求(KMP)(sum Next_i)

    问题转化

    考虑到可以离线

    于是,我们就可以用一个比较常用的技巧,从每个版本向由其转移到的版本连边,然后从(0)的各个子节点出发遍历一遍操作树,且每操作完一个节点的子树就清空当前贡献。

    则我们就只用考虑如何计算每次新加入的一段字符对答案的影响即可,问题一下简单了许多。

    暴力

    接下来我们考虑如何计算新加入的一段字符对答案的影响,首先思考如何暴力。

    可以暴力跳(KMP)

    假设我们加入了(x)个字符(c),则我们跳(Next)数组来枚举之前每次加入字符(c)的操作,假设当前枚举的操作加入了(y)个字符(c)

    则对于(x=y),它们两个恰好可以匹配。

    否则,就说明当前加入的前(min(x,y))个字符可以与之前加入的这一段计算答案。

    注意,仅限于计算答案,而不能匹配,因为这两段是无法匹配对后续操作造成影响的。

    这个算法使用了(KMP),看起来是一个很珂学的做法,那么为什么说它是暴力呢?

    因为(KMP)的复杂度是均摊的,而我们为了方便将其放在操作树上搞,这样复杂度就不正确了,随便被卡。

    主席树优化

    回顾我们前面提到过的计算答案的方式,就是找到之前某一段长度为(y)的字符(c),然后前(min(x,y))个字符可以一起算答案。

    显然这里每个字符只能算一次答案,且由于(Next)数组的定义,我们必须找最靠后的。

    则我们对于插入一段(x)个字符(c),直接在关于(c)主席树上修改([1,x])这段区间值为这次操作前已插入的字符个数,并在第(x)个位置上记录下(Next)为当前操作。

    查询时同样在主席树上以类似方式查询即可。

    然后要处理下一个操作时我们就将当前信息(根节点信息等)复制到下一个操作要用的数组,然后就可以继续处理了。

    具体实现详见代码。

    代码

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 100000
    #define M 10000
    #define Log 20
    #define X 998244353
    #define Gmax(x,y) (x<(y)&&(x=(y)))
    #define min(x,y) ((x)<(y)?(x):(y))
    #define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
    #define Memset(x,y) memset(x,y,sizeof(x))
    #define Memcpy(x,y) memcpy(x,y,sizeof(x))
    #define Inc(x,y) ((x+=(y))>=X&&(x-=X))
    #define Dec(x,y) ((x-=(y))<0&&(x+=X))
    #define XSum(x,y) ((x)+(y)>=X?(x)+(y)-X:(x)+(y))
    #define XSub(x,y) ((x)<(y)?(x)-(y)+X:(x)-(y))
    using namespace std;
    int n,ee,a[N+5],b[N+5],p[N+5],lnk[N+5];struct edge {int to,nxt;}e[N+5];
    class FastIO
    {
    	private:
    		#define FS 100000
    		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
    		#define pc(c) (C^FS?FO[C++]=c:(fwrite(FO,1,C,stdout),FO[(C=0)++]=c))
    		#define tn (x<<3)+(x<<1)
    		#define D isdigit(c=tc())
    		int T,C;char c,*A,*B,FI[FS],FO[FS],S[FS];
    	public:
    		I FastIO() {A=B=FI;}
    		Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
    		Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
    		Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
    		Tp I void writeln(Con Ty& x) {write(x),pc('
    ');}
    		I void readc(char& x) {W(isspace(x=tc()));}
    		I void clear() {fwrite(FO,1,C,stdout),C=0;}
    		#undef D
    }F;
    class OperatorTreeDfser//操作树上DFS
    {
    	private:
    		#define Sum(x) ((1LL*(x)*((x)+1)>>1)%X)
    		int a1,b1,T,s[N+5],rt[N+5][30],Mx[N+5][30],ans[N+5];
    		class ChairmanTree//主席树
    		{
    			private:
    				#define L l,mid,O[rt].S[0]
    				#define R mid+1,r,O[rt].S[1] 
    				#define PU(x) (O[x].Su=XSum(O[O[x].S[0]].Su,O[O[x].S[1]].Su))
    				#define PD(x) O[x].F&&
    				(
    					O[++tot]=O[O[x].S[0]],O[x].S[0]=tot,U(O[x].S[0],mid-l+1,O[x].F),
    					O[++tot]=O[O[x].S[1]],O[x].S[1]=tot,U(O[x].S[1],r-mid,O[x].F),
    					O[x].F=0
    				)//下传标记
    				#define U(x,s,v) (O[x].Su=1LL*(s)*(v)%X,O[x].F=v)
    				int tot;struct node {int Nxt,Su,F,S[2];}O[N*Log*3+5];
    				I void upt(CI l,CI r,int& rt,CI x,CI v,CI nxt)//区间修改值,单点修改nxt
    				{
    					if(O[++tot]=O[rt],rt=tot,r<x) return (void)U(rt,r-l+1,v);
    					if(l==r) return (void)(U(rt,1,v),O[rt].Nxt=nxt);RI mid=l+r>>1;PD(rt),
    					x>mid&&(upt(R,x,v,nxt),0),upt(L,x,v,nxt),PU(rt);
    				}
    				I int qry(CI l,CI r,CI rt,CI x,int& sum)//区间询问和,单点询问nxt
    				{
    					if(l==r) return Inc(sum,O[rt].Su),O[rt].Nxt;RI mid=l+r>>1;PD(rt);
    					return x<=mid?qry(L,x,sum):(Inc(sum,O[O[rt].S[0]].Su),qry(R,x,sum));
    				}
    			public:
    				I void Clear() {tot=0;}//清空节点数
    				I void Update(int& rt,CI x,CI v,CI nxt) {upt(1,M,rt,x,v,nxt);}
    				I int Query(CI rt,CI x,int& sum) {return qry(1,M,rt,x,sum);}
    		}C;
    	public:
    		I void Clear() {s[1]=0,C.Clear(),Memset(rt,0),Memset(Mx,0);}//清空信息
    		I int GetAns(CI x) {return ans[x];}//求对应的答案
    		I void dfs(CI x)//操作树上DFS
    		{
    			RI i,v=a[x],u=b[x],nxt=0;++T^1?//如果不是第一次操作
    			(
    				Inc(ans[x],Sum(min(v,Mx[T][u]))),nxt=C.Query(rt[T][u],v,ans[x]),//询问信息
    				!nxt&&a1<v&&b1==u&&(nxt=1,v>Mx[T][u]&&Inc(ans[x],1LL*a1*(v-Mx[T][u])%X))//处理特殊情况
    			):(a1=v,b1=u,ans[x]=Sum(v-1));//第一次操作特殊处理
    			Gmax(Mx[T][u],v),s[T]+=v,C.Update(rt[T][u],v,s[T-1],T);//修改
    			for(i=lnk[x];i;i=e[i].nxt)//枚举儿子遍历
    				Memcpy(rt[T+1],rt[nxt+1]),Memcpy(Mx[T+1],Mx[nxt+1]),//复制信息
    				ans[e[i].to]=ans[x],s[T+1]=s[T],dfs(e[i].to);//复制信息,然后处理
    			--T;//弹去这一次操作
    		}
    }D;
    int main()
    {
    	RI i,op,x,t=0;char c;for(F.read(n),i=1;i<=n;++i)
    		F.read(op,x),op^2?(a[p[i]=++t]=x,F.readc(c),b[t]=c&31,add(p[i-1],p[i])):(p[i]=p[x]);//读入+建边
    	for(i=lnk[0];i;i=e[i].nxt) D.Clear(),D.dfs(e[i].to);//从0的每个儿子出发开始遍历
    	for(i=1;i<=n;++i) F.writeln(D.GetAns(p[i]));return F.clear(),0;//输出答案
    }
    
  • 相关阅读:
    【css】rem及其替换方案
    【css】如何实现环形进度条
    【js】我们需要无限滚动列表吗?
    【js】再谈移动端的模态框实现
    【js】callback时代的变更
    【js】为什么要使用react+redux
    【js】JavaScript parser实现浅析
    【css】回想下经典的布局
    【JS】温故知新: 从parseInt开始
    【渲染原理】浏览器渲染原理的个人整理
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu5287.html
Copyright © 2011-2022 走看看