拖更了一个暑假。
P6492
很妙的线段树阿。
对于修改,我们无需用 lazy tag,只要每次跑到叶子节点去直接修改即可。
对于询问,答案即为树根的信息,因为它每次询问的都是整个区间。
最难的是 pushup
部分:
我们需要维护三个东西,ans,lx,rx
,分别表示当前节点的 整个串的最长合法串 / 左端点开头的最长合法串 / 右端点开头的最长合法串;
ans
可以取自 左孩子的 ans
/ 右孩子的 ans
/ 左孩子的 rx
\(+\) 右孩子的 lx
(必须满足连接处的字符不一样)。
lx
可以取自 左孩子的 lx
\(+\) 右孩子的 lx
(要加上后面部分必须满足左孩子的 lx
是整个左孩子区间且连接处的字符不一样)。
rx
可以取自 右孩子的 rx
\(+\) 左孩子的 lx
(要加上后面部分必须满足右孩子的 rx
是整个右孩子区间且连接处的字符不一样)。
总结:本题维护了线段树上的多个信息,且涉及到了跨区间合并信息,十分具有启发性。
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,q;
int a[N];
struct SGT{
int ans,lx,rx;
}tree[N];
void pushup(int p,int lt,int rt){
int mid=(lt+rt)>>1;
tree[p].lx=tree[p*2].lx;
if(tree[p*2].lx==mid-lt+1&&a[mid]!=a[mid+1])
tree[p].lx+=tree[p*2+1].lx;
tree[p].rx=tree[p*2+1].rx;
if(tree[p*2+1].rx==rt-mid&&a[mid]!=a[mid+1])
tree[p].rx+=tree[p*2].rx;
tree[p].ans=max(tree[p*2].ans,tree[p*2+1].ans);
if(a[mid]!=a[mid+1])
tree[p].ans=max(tree[p].ans,tree[p*2].rx+tree[p*2+1].lx);
}
void bld(int p,int lt,int rt){
if(lt==rt){
tree[p].ans=tree[p].lx=tree[p].rx=1;
return;
}
int mid=(lt+rt)>>1;
bld(p*2,lt,mid);
bld(p*2+1,mid+1,rt);
pushup(p,lt,rt);
}
void upd(int p,int lt,int rt,int x){
if(lt>x||rt<x)
return;
else if(lt==x&&rt==x){
a[x]^=1;
return;
}
int mid=(lt+rt)>>1;
upd(p*2,lt,mid,x);
upd(p*2+1,mid+1,rt,x);
pushup(p,lt,rt);
}
int main(){
ios::sync_with_stdio(0);
cin>>n>>q;
bld(1,1,n);
while(q--){
int x;
cin>>x;
upd(1,1,n,x);
cout<<tree[1].ans<<'\n';
}
return 0;
}
P2894
和上一题很相似。
这题有区间修改,所以要 lazy tag(注意有三种情形:无标记、空房、非空房)。
pushup
同上,只是要去掉「满足连接处的字符不一样」这个条件(有个问题,为什么不要判连接处的两个字符都为 \(0\)?第一判不了,因为这题是区间修改;第二因为 ans,lx,rx
会在打 lazy tag 时进行重置,因此无需担心正确性问题)。
询问的时候就找 左孩子 / 右孩子 / 中间拼接 这三个地方是否有一个地方的答案大于等于 \(x\),哪里满足就去哪里,一个都没有就无解。
总结:与上题类似,略。
code
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,q;
int a[N],tag[N];
struct SGT{
int ans,lx,rx;
}tree[N];
void addtag(int p,int lt,int rt,int val){
tag[p]=val;
if(val==1)
tree[p].ans=tree[p].lx=tree[p].rx=0;
else
tree[p].ans=tree[p].lx=tree[p].rx=rt-lt+1;
}
void pushdown(int p,int lt,int rt){
if(!tag[p])
return;
int mid=(lt+rt)>>1;
addtag(p*2,lt,mid,tag[p]);
addtag(p*2+1,mid+1,rt,tag[p]);
tag[p]=0;
}
void pushup(int p,int lt,int rt){
int mid=(lt+rt)>>1;
tree[p].lx=tree[p*2].lx;
if(tree[p*2].lx==mid-lt+1)
tree[p].lx+=tree[p*2+1].lx;
tree[p].rx=tree[p*2+1].rx;
if(tree[p*2+1].rx==rt-mid)
tree[p].rx+=tree[p*2].rx;
tree[p].ans=max({tree[p*2].ans,tree[p*2+1].ans,tree[p*2].rx+tree[p*2+1].lx});
}
void bld(int p,int lt,int rt){
if(lt==rt){
tree[p].ans=tree[p].lx=tree[p].rx=1;
return;
}
int mid=(lt+rt)>>1;
bld(p*2,lt,mid);
bld(p*2+1,mid+1,rt);
pushup(p,lt,rt);
}
void upd(int p,int lt,int rt,int ql,int qr,int val){
if(lt>qr||rt<ql)
return;
else if(ql<=lt&&rt<=qr){
addtag(p,lt,rt,val);
return;
}
pushdown(p,lt,rt);
int mid=(lt+rt)>>1;
upd(p*2,lt,mid,ql,qr,val);
upd(p*2+1,mid+1,rt,ql,qr,val);
pushup(p,lt,rt);
}
int qry(int p,int lt,int rt,int x){
if(lt==rt)
return lt;
pushdown(p,lt,rt);
int mid=(lt+rt)>>1;
if(tree[p*2].ans>=x)
return qry(p*2,lt,mid,x);
if(tree[p*2].rx+tree[p*2+1].lx>=x)
return mid-tree[p*2].rx+1;
if(tree[p*2+1].ans>=x)
return qry(p*2+1,mid+1,rt,x);
return 0;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>q;
bld(1,1,n);
while(q--){
int op,x,y;
cin>>op>>x;
if(op==1){
int pos=qry(1,1,n,x);
if(pos!=0)
upd(1,1,n,pos,pos+x-1,1);
cout<<pos<<'\n';
}
else{
cin>>y;
upd(1,1,n,x,x+y-1,2);
}
}
return 0;
}
CF620E
看到数颜色差点以为是莫队。。。
这题是维护子树信息,我们求出 dfs 序即可转化为序列问题。
然后它询问子树内节点的颜色种类数,我们考虑状压,将每个子树内的颜色情况压缩为一个二进制数(即有第 \(i\) 种颜色则第 \(i\) 为 \(1\),否则为 \(0\)),然后每个节点的状态即为两个子节点的状态或起来的值,每次询问时回答当前区间状态中 \(1\) 的个数即可。
总结:本题运用了 dfs 序将树上问题转化为序列问题、以及二进制压缩的技巧,尤其是后者十分精妙,需要牢记,可以在数颜色这一类的题目中使用。