Living-Dream 系列笔记 第77期

拖更了一个暑假。

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 序将树上问题转化为序列问题、以及二进制压缩的技巧,尤其是后者十分精妙,需要牢记,可以在数颜色这一类的题目中使用。

posted @   _XOFqwq  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示