题解 P5599 【【XR-4】文本编辑器】
题目传送门。
题意简述:给定长度为
n 的文本串a 和有m 个单词的字典s_i 。q 次操作,每次求出字典内所有单词在a[l,r] 的出现次数,或将a[l,r] 替换为t 不断重复的结果。
假设没有修改操作,且
查询时,因为
Subtask #3:如果有修改操作呢?事情就变得无比麻烦了。昨天尝试写了一下,甚至比正解还难写(doge)。
首先得处理这个循环的 当然,我不知道怎么处理,所以看了眼 s_r_f 的题解。 注意到题目中说 “与文件相比,单词的长度是非常小的(
首先,
同样的,对于
接下来处理中间那一大坨循环的
当然,说起来简单,还有很多需要注意的地方:
-
Q1:如何维护循环节?
A1:一个循环节信息主要就是循环节的开头位置,长度和循环节内每个位置的值。 因此,需要维护每一个循环节
id (表示这是第id 次修改形成的循环节)的开头位置lp_{id} ,长度len_{id} ,以及每一个位置i 上的值d_{id,i} 。循环节内位置从0 开始标号。在线段树区间
[l,r] 的懒标记内维护两个信息:id 和hd 。id 表示该标记是第id 次修改形成的循环节(不是第id 个循环节),hd 表示该区间的起始位置l 在该循环节中的位置。在 pushdown 的时候,假设我们传入了三个参数
l,r,x 表示从区间[l,r] 下传,该区间在线段树内编号为x 。先求出左区间和右区间的分界点m=\lfloor\frac{l+r}{2}\rfloor ,再求出右区间[m+1,r] 的起始位置m+1 在循环节的位置,记为mid ,则不难求出mid=(hd+(m+1)-l)\bmod len_{id} ,然后将id,hd 与id,mid 的懒标记分别赋给[l,m] 与[m+1,r] ,并更新其维护的区间f 之和:void push(int l,int r,int x){ // pushdown if(laz[x].id){ int m=l+r>>1; int id=laz[x].id,hd=laz[x].hd; int mid=(hd+(m-l+1))%len[id]; mark(l,m,x<<1,id,hd); mark(m+1,r,x<<1|1,id,mid); val[x]=val[x<<1]+val[x<<1|1]; laz[x].id=0; } }其中
mark(l,r,x,id,hd)表示给在线段树内编号为x 的区间[l,r] 打上id,hd 的懒标记。此时我们求出该区间末位置在循环节id 中的位置tl=(hd+(r-l))\bmod len_{id} ,并求出[l,r] 间共有多少个循环节id ,然后更新即可。void mark(int l,int r,int x,int id,int hd){ // mark lazytag & update laz[x]={id,hd}; int ori=l-hd,tl=(r-ori)%len[id],rid=(r-ori)/len[id]; if(rid==0)val[x]=pre[id][tl]-(hd?pre[id][hd-1]:0); else val[x]=pre[id][tl]+(rid*sum[id]-(hd?pre[id][hd-1]:0)); }一些代码说明:
ori 表示l 所在的循环节的开头,并假设其为第0 个循环节。rid 表示r 在第rid 个循环节内。需要注意的是,这里的pre_{id,i} 是pre_{id} 的前缀和。sum_{id} 表示循环节id 所有位置上的和,即sum_{id}=pre_{id,len_{id}-1} 。 -
Q2:暴力匹配时怎么求出
a 当前的内容?首先,需要求出的
a 是一段区间,设其为[l,r] 。那么在线段树上按序遍历每一个区间[x,x]\ (l\leq x\leq r) 。具体来说,就算当前区间[l',r']\subseteq[l,r] 也不返回,直到访问到叶子结点[x,x] 。可以证明这样访问的时间复杂度为\mathcal{O}(\log n+len) 。又因为需要求出的长度不超过2L ,因此总时间复杂度为\mathcal{O}(q(\log n+L)) 。若该区间有标记
id,hd ,那么该位置上的字符应为t_{id,hd} ,即第id 次修改时给出的字符串t_{id} 的第hd 个位置上的字符。否则该位置上的字符应为a_x 。void forms(int l,int r,int ql,int qr,int x){ // form current string [ql,qr] if(ql>qr)return; if(l==r){ if(laz[x].id)tmp+=qt[laz[x].id][laz[x].hd]; else tmp+=ct[l-1]; return; } int m=l+r>>1; push(l,r,x); if(ql<=m)forms(l,m,ql,qr,x<<1); if(m<qr)forms(m+1,r,ql,qr,x<<1|1); } -
Q3:暴力匹配求出
f 后怎么在线段树上修改?A3:同样,需要更新的
f 也是一段区间[l,r] 。如法炮制,按序遍历每一个区间[x,x]\ (l\leq x\leq r) ,并修改f 的值即可。由于求出
a 的内容时需要每个节点的标记,而暴力修改f 的位置有的需要打标记,如[l,l+L-2] ,有的不需要,如[r+1,r+L-1] ,所以需要分情况讨论。void modifyc(int l,int r,int ql,int qr,int x,bool tg){ // brute force : change if(ql>qr)return; if(l==r){ val[x]=f[l]; if(tg)laz[x]={id,(l-lpos)%len[id]}; return; } int m=l+r>>1; push(l,r,x); if(ql<=m)modifyc(l,m,ql,qr,x<<1,tg); if(m<qr)modifyc(m+1,r,ql,qr,x<<1|1,tg); val[x]=val[x<<1]+val[x<<1|1]; }
还有一些注意点(踩过的坑):
- 正如 s_r_f 所说,为了使代码更简洁,我们可以将左边暴力匹配的区间
[l-L+1,l+L-2] 的右端点向右稍微移动一点,使得新的右端点led 刚好是一段循环节的结尾。同时,为了求出循环节,还要再向右匹配T 个位置。 - 如果区间的长度太小,可以直接暴力匹配。
- 需要先求出
sum_{id} 再更新,因为更新时需要用到sum_{id} 。
这样,时间复杂度为
看到这里,你可能以为我已经讲完了。实际上并没有,这只是
注意到字典是固定的,所以我们对其建立 AC 自动机。那么只需要将
注意到
剩下来就和
/*
Powered by C++11.
Author : Alex_Wei.
*/
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6+5;
const int L=50+5;
const int Q=1e5+5;
const int S=2e5+5;
// basic variables
int lc,nw,q;
ll mp[1<<7],f[N];
string ct;
struct ACAM{
int cnt,son[S][62],fa[S],val[S];
void ins(string s){
int p=0;
for(char it:s){
if(!son[p][mp[it]])son[p][mp[it]]=++cnt;
p=son[p][mp[it]];
} val[p]++;
} void build(){
queue <int> q;
for(int i=0;i<62;i++)if(son[0][i])q.push(son[0][i]);
while(!q.empty()){
int t=q.front(); q.pop();
for(int i=0;i<62;i++)
if(son[t][i])fa[son[t][i]]=son[fa[t]][i],q.push(son[t][i]);
else son[t][i]=son[fa[t]][i];
val[t]+=val[fa[t]];
}
} void run(){ // get f
int p=0;
for(int i=1;i<=lc;i++)f[i]=val[p=son[p][mp[ct[i-1]]]];
}
}ac;
// query variables
ll id,len[Q],sum[Q];
vector <ll> pre[Q];
string qt[Q];
// lazytag & Segment Tree
string tmp;
struct lazy{
int id,hd;
}; struct SegTree{
ll val[N<<2],lpos;
lazy laz[N<<2];
void build(int l,int r,int x){
if(l==r){
val[x]=f[l];
return;
} int m=l+r>>1;
build(l,m,x<<1),build(m+1,r,x<<1|1);
val[x]=val[x<<1]+val[x<<1|1];
} void mark(int l,int r,int x,int id,int hd){ // mark lazytag & update
laz[x]={id,hd};
int ori=l-hd,tl=(r-ori)%len[id],rid=(r-ori)/len[id];
if(rid==0)val[x]=pre[id][tl]-(hd?pre[id][hd-1]:0);
else val[x]=pre[id][tl]+(rid*sum[id]-(hd?pre[id][hd-1]:0));
} void push(int l,int r,int x){ // pushdown
if(laz[x].id){
int m=l+r>>1;
int id=laz[x].id,hd=laz[x].hd;
int mid=(hd+(m-l+1))%len[id];
mark(l,m,x<<1,id,hd);
mark(m+1,r,x<<1|1,id,mid);
laz[x].id=0;
}
} void modifyt(int l,int r,int ql,int qr,int x){ // tag
if(ql<=l&&r<=qr){
mark(l,r,x,id,(l-lpos)%len[id]);
return;
} int m=l+r>>1; push(l,r,x);
if(ql<=m)modifyt(l,m,ql,qr,x<<1);
if(m<qr)modifyt(m+1,r,ql,qr,x<<1|1);
val[x]=val[x<<1]+val[x<<1|1];
} void modifyc(int l,int r,int ql,int qr,int x,bool tg){ // brute force : change
if(ql>qr)return;
if(l==r){
val[x]=f[l];
if(tg)laz[x]={id,(l-lpos)%len[id]};
return;
} int m=l+r>>1; push(l,r,x);
if(ql<=m)modifyc(l,m,ql,qr,x<<1,tg);
if(m<qr)modifyc(m+1,r,ql,qr,x<<1|1,tg);
val[x]=val[x<<1]+val[x<<1|1];
} ll query(int l,int r,int ql,int qr,int x){
if(ql>qr)return 0;
if(ql<=l&&r<=qr)return val[x];
ll m=l+r>>1,ans=0; push(l,r,x);
if(ql<=m)ans+=query(l,m,ql,qr,x<<1);
if(m<qr)ans+=query(m+1,r,ql,qr,x<<1|1);
return ans;
} void forms(int l,int r,int ql,int qr,int x){ // form current string [ql,qr]
if(ql>qr)return;
if(l==r){
if(laz[x].id)tmp+=qt[laz[x].id][laz[x].hd];
else tmp+=ct[l-1];
return;
} int m=l+r>>1; push(l,r,x);
if(ql<=m)forms(l,m,ql,qr,x<<1);
if(m<qr)forms(m+1,r,ql,qr,x<<1|1);
}
}st;
int main(){
// freopen("P5599_5.in","r",stdin);
// freopen("P5599_5.out","w",stdout);
// mp
for(int i='A';i<='Z';i++)mp[i]=i-'A';
for(int i='a';i<='z';i++)mp[i]=26+(i-'a');
for(int i='0';i<='9';i++)mp[i]=52+(i-'0');
// read & init
cin>>lc>>nw>>q>>ct;
for(int i=1;i<=nw;i++){
string wrd;
cin>>wrd,ac.ins(wrd);
} ac.build(),ac.run(),st.build(1,lc,1);
// solve
for(int i=1;i<=q;i++){
int op,l,r,ls; scanf("%d%d%d",&op,&l,&r),ls=r-l+1,st.lpos=l;
if(op==1){
ll rpos=min(r,l+L),ans=0,p=0;
tmp="",st.forms(1,lc,l,rpos,1);
for(int i=l;i<=rpos;i++)ans+=ac.val[p=ac.son[p][mp[tmp[i-l]]]];
printf("%lld\n",ans+st.query(1,lc,rpos+1,r,1));
} else{
string t; cin>>t,len[++id]=t.size(),pre[id].resize(len[id]);
int lpos=max(1,l-L+1),rpos=min(lc,r+L-1),p=0;
if(ls<=L*2+len[id]*2){
tmp="",st.forms(1,lc,lpos,rpos,1); // get current string
for(int i=lpos;i<=rpos;i++){
char it=(i<l||i>r?tmp[i-lpos]:t[(i-l)%len[id]]);
p=ac.son[p][mp[it]];
if(i>=l)f[i]=ac.val[p];
} st.modifyc(1,lc,l,r,1,1),st.modifyc(1,lc,r+1,rpos,1,0);
} else{
// front section : [l-L+1,l+L-1]
int led=l+L-1,rbg=r-L+1;
while((led-l)%len[id])led++;
tmp="",st.forms(1,lc,lpos,l-1,1);
for(int i=lpos;i<led+len[id];i++){
char it=(i<l?tmp[i-lpos]:t[(i-l)%len[id]]);
p=ac.son[p][mp[it]];
if(i>=l){
if(i<led)f[i]=ac.val[p];
else pre[id][i-led]=(i>led?pre[id][i-led-1]:0)+ac.val[p];
}
} sum[id]=pre[id][len[id]-1];
st.modifyc(1,lc,l,led-1,1,1),st.modifyt(1,lc,led,r,1);
// back section
tmp="",st.forms(1,lc,r+1,rpos,1),p=0;
for(int i=rbg;i<=rpos;i++){
char it=(i>r?tmp[i-r-1]:t[(i-l)%len[id]]);
p=ac.son[p][mp[it]];
if(i>r)f[i]=ac.val[p];
} st.modifyc(1,lc,r+1,rpos,1,0);
} qt[id]=t;
}
}
return 0;
}
如果您看懂了这篇题解就点个赞吧 qwq。