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 序将树上问题转化为序列问题、以及二进制压缩的技巧,尤其是后者十分精妙,需要牢记,可以在数颜色这一类的题目中使用。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】