点此看题面

大致题意: 每次往一个字符串末尾加上\(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('\n');}
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;//输出答案
}

【洛谷5287】[HNOI2019] JOJO(主席树优化KMP)的更多相关文章

  1. 洛谷P2617 Dynamic Rankings (主席树)

    洛谷P2617 Dynamic Rankings 题目描述 给定一个含有n个数的序列a[1],a[2],a[3]--a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a ...

  2. 洛谷P3567 KUR-Couriers [POI2014] 主席树/莫队

    正解:主席树/莫队 解题报告: 传送门! 这题好像就是个主席树板子题的样子,,,? 毕竟,主席树的最基本的功能就是,维护一段区间内某个数字的个数 但是毕竟是刚get到主席树,然后之前做的一直是第k大, ...

  3. 洛谷P3567[POI2014]KUR-Couriers(主席树+二分)

    题意:给一个数列,每次询问一个区间内有没有一个数出现次数超过一半 题解: 最近比赛太多,都没时间切水题了,刚好日推了道主席树裸题,就写了一下 然后 WA80 WA80 WA0 WA90 WA80 ?? ...

  4. 洛谷P3567 [POI2014]KUR-Couriers 主席树

    挺裸的,没啥可讲的. 不带修改的主席树裸题 Code: #include<cstdio> #include<algorithm> using namespace std; co ...

  5. 洛谷$P3302$ 森林 $[SDOI2013]$ 主席树

    正解:主席树 解题报告: 传送门! 口胡一时爽代码火葬场 这题想法不难,,,但显然的是代码应该还挺难打的 但反正我也不放代码,就写下题解趴$QwQ$ 第一问就是个$Count\ on\ a\ tree ...

  6. 洛谷P4602 [CTSC2018]混合果汁(主席树)

    题目描述 小 R 热衷于做黑暗料理,尤其是混合果汁. 商店里有 nn 种果汁,编号为 0,1,\cdots,n-10,1,⋯,n−1 . ii 号果汁的美味度是 d_idi​ ,每升价格为 p_ipi ...

  7. 洛谷P2617 Dynamic Rankings 主席树 单点修改 区间查询第 K 大

    我们将线段树套在树状数组上,查询前预处理出所有要一起移动的节点编号,并在查询过程中一起将这些节点移到左右子树上. Code: #include<cstdio> #include<cs ...

  8. 洛谷4137 mex题解 主席树

    题目链接 虽然可以用离线算法水过去,但如果强制在线不就gg了. 所以要用在线算法. 首先,所有大于n的数其实可以忽略,因为mex的值不可能大于n 我们来设想一下,假设已经求出了从0到n中所有数在原序列 ...

  9. 洛谷 P3384 【模板】树链剖分-树链剖分(点权)(路径节点更新、路径求和、子树节点更新、子树求和)模板-备注结合一下以前写的题目,懒得写很详细的注释

    P3384 [模板]树链剖分 题目描述 如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作: 操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节 ...

随机推荐

  1. get 与 post 区别

    Http定义了与服务器交互的不同方法,最基本的方法有4种,分别是GET,POST,PUT,DELETE.URL全称是资源描述符,我们可以这样认为:一个URL地址,它用于描述一个网络上的资源,而HTTP ...

  2. PIE SDK波段合成

    1.算法功能简介 波段合成功能主要用于将多幅图像合并为一个新的多波段图像(即波段的叠加打包,构建一个新的多波段文件),从而可根据不同的用途选择不同波长范围内的波段合成 RGB 彩色图像. PIE支持算 ...

  3. PHP foreach ($arr as &amp;$value)

    foreach ($arr as &$value) 看到一个有意思的东西: <?php $arr = ['1', '2', '3', '4']; foreach ($arr as &am ...

  4. 委托delegate 泛型委托action<> 返回值泛型委托Func<> 匿名方法 lambda表达式 的理解

    1.使用简单委托 namespace 简单委托 { class Program { //委托方法签名 delegate void MyBookDel(int a); //定义委托 static MyB ...

  5. Android触摸事件传递机制

    简单梳理一下Android触摸事件传递机制的知识点. 一.View与ViewGroup的关系 View和ViewGroup二者的继承关系如下图所示: View是Android中最基本的一种UI组件,它 ...

  6. 在SourceTree中使用Git submodule

    在開發的過程中我們的項目可能會引用其他的版本庫中的代碼, 例如公司已經累積了一套公用的函式庫, 被多個項目調用;  很顯然地, 不能把公用函式庫的文件直接放到我們開發中的項目中, 這樣不但項目的冗餘, ...

  7. 【iptables】规则的试验和查看

    1.存在桥接,查看桥接访客网络规则 ebtables -L 可以看到对不同的链的策略

  8. Unity3D第一课之自转与公转

    1.物体公转,即围绕一个中心物体旋转 public class gongzhuan : MonoBehaviour { public GameObject Axis;//轴,用于选择围绕中心 publ ...

  9. Json/Xml简介和处理模型

    JSON json简介 JSON是一种基于文本的数据交换格式,源自JavaScript,用于Web服务和其他连接的应用程序.以下部分介绍了JSON语法,JSON使用概述以及生成和解析JSON的最常用方 ...

  10. linux基础-linux和unix的区别

    有时候我们对天天使用的Linux指令,只知道怎么用,却分不清概念用法区别,我觉得很有必要整理整理大家熟视无睹的一些linux概念区别. 首先说说unix和linux的区别: linux和unix的最大 ...