LCT
#define lc c[x][0]
#define rc c[x][1]
const int N=3e5+10;
int n,m,v[N],f[N],c[N][2],s[N],r[N],st[N];
inline bool nroot(int x){
return c[f[x]][0]==x||c[f[x]][1]==x;
}//x 是不是一个 splay 的根
inline void pushup(int x){
s[x]=s[lc]^s[rc]^v[x];
}
inline void pushr(int x){
swap(lc,rc);
r[x]^=1;
}
inline void pushdown(int x){
if(r[x]){
if(lc) pushr(lc);
if(rc) pushr(rc);
r[x]=0;
}
}
inline void rotate(int x){
int y=f[x],z=f[y],k=c[y][1]==x,w=c[x][!k];
if(nroot(y)) c[z][c[z][1]==y]=x;
c[x][!k]=y;c[y][k]=w;
if(w) f[w]=y;f[y]=x;f[x]=z;
pushup(y);
pushup(x);
}//旋转一次,和splay差不多,注意不要涉及 0 即可
rotate
的前提是
inline void splay(int x){
int y=x,z=0;
st[++z]=y;
while(nroot(y)) st[++z]=y=f[y];//用栈存下来一到根的路径,然后从上到下 pushdown
while(z) pushdown(st[z--]);
while(nroot(x)){
y=f[x];z=f[y];
if(nroot(y)) rotate((c[y][0]==x)^(c[z][0]==y)?x:y);
rotate(x);
}
pushup(x);
}
inline void access(int x){
for(int y=0;x;x=f[y=x]){
splay(x);rc=y;pushup(x);
}
}//打通 x 到根的路径,并且最后 x 是 splay 中中序遍历最大的,x 到根是一条链
inline void makeroot(int x){
access(x);splay(x);
pushr(x);
}//换根,打通到根的路,转上去,打标记
inline int findroot(int x){
access(x);splay(x);
while(lc) pushdown(x),x=lc;
splay(x);
return x;
}//找"真实"的树中的根
inline void split(int x,int y){
makeroot(x);
access(y);
splay(y);
}//提取路径,将 x 换根,打通 y 到 x 的路径,再将 y 转上去成为根
inline void link(int x,int y){
makeroot(x);
if(findroot(y)!=x) f[x]=y;
}//连边,判断连通性
inline void cut(int x,int y){
makeroot(x);
if(findroot(y)==x && f[y]==x && !c[y][0]){
f[y]=c[x][1]=0;
pushup(x);
}
}//判断的是否有边的存在,然后删除边
namespace LCT{
#define lson ch[x][0]
#define rson ch[x][1]
int s[N],st[N],ch[N][2],r[N],f[N];
inline void pushup(int x) {s[x]=s[lson]^s[rson]^a[x];}
inline void pushr(int x) {r[x]^=1;swap(lson,rson);}
inline void pushdown(int x) {if(r[x]) {if(lson) pushr(lson);if(rson) pushr(rson);r[x]=0;}}
inline int noroot(int x) {return (ch[f[x]][0]==x)||(ch[f[x]][1]==x);}
inline void rotate(int x){
int y=f[x],z=f[y],k=ch[x][!(ch[y][1]==x)],w1=(ch[z][1]==y),w2=(ch[y][1]==x);
if(noroot(y)) ch[z][w1]=x;
f[x]=z;f[y]=x;ch[x][!w2]=y;ch[y][w2]=k;
if(k) f[k]=y;
pushup(y);pushup(x);
}
inline void splay(int x){
int y=x,z=0;
while(noroot(y)) st[++z]=y,y=f[y];
st[++z]=y;
while(z) pushdown(st[z--]);
while(noroot(x)){
y=f[x],z=f[y];
if(noroot(y)) rotate((ch[z][1]==y)^(ch[y][1]==x)?x:y);
rotate(x);
}
pushup(x);
}
inline void access(int x){
for(int y=0;x;x=f[y=x]){
splay(x);rson=y;pushup(x);
}
}
inline void makeroot(int x){
access(x);
splay(x);
pushr(x);
}
inline int findroot(int x){
access(x);splay(x);
pushdown(x);
while(lson) x=lson,pushdown(lson);
splay(x);
return x;
}
inline void split(int x,int y){
makeroot(x);
access(y);
splay(y);
}
inline void link(int x,int y){
makeroot(x);
if(findroot(y)!=x) f[x]=y;
}
inline void cut(int x,int y){
makeroot(x);
if(findroot(y)==x && !ch[y][0] && f[y]==x){
f[y]=ch[x][1]=0;
pushup(x);
return ;
}
}
#undef lson
#undef rson
}using namespace LCT;
维护链信息
动态维护连通性&双连通分量
注意:如果用并查集缩点一定要注意每次用节点的时候先 find
出真实的缩后的节点。
维护边权(生成树)
维护子树信息
注意
rotate,splay
),那么就不用特殊考虑虚边儿子的变化。
#558. 「Antileaf's Round」我们的 CPU 遭到攻击
int ch[N][2],f[N],sum[N];
int len[N],lmx[N],rmx[N],Mx[N],w[N];
//“点权”,节点所在链最浅的节点的和白点的最远距离,节点所在链最深的节点的和白点的最远距离,节点所维护的子树包括虚儿子白色点对的最远距离,是否是白点
set
维护一下,但是如果总是进进出出,常数就会非常大,所以我们搞个标记,维护一下:
int lfir[N],lsec[N],pfir[N];
multiset<int> l[N],p[N];
pushup
的代码应该就能懂了
inline void pushup(int x) {
sum[x]=sum[lson]+sum[rson]+len[x];
//权值和
int xushu=max(w[x],lfir[x]);
//虚链能延长出去的最大值,包括自己本身
int L=max(xushu,rmx[lson]+len[x]);
//自己能从左边或者虚链走得到的最大值
int R=max(xushu,lmx[rson]);
//同上
lmx[x]=max(lmx[lson],sum[lson]+len[x]+R);
//不走 x ,走 x
rmx[x]=max(rmx[rson],L+sum[rson]);
//应该也比较好懂
Mx[x]=max(rmx[lson]+len[x]+R,lmx[rson]+L);
//固定左/右实链,求最大值
Mx[x]=max(max(Mx[lson],Mx[rson]),Mx[x]);
//直接通过左右子树更新最大值
Mx[x]=max(Mx[x],lfir[x]+lsec[x]);
//通过最大虚链和次大虚链更新最大值
Mx[x]=max(Mx[x],pfir[x]);
//直接通过虚儿子更新最大值
if(!w[x]) Mx[x]=max(0,max(lfir[x],Mx[x]));
//如果本身是白点,那么自己就可以做端点,拿一个最长虚链出来更新即可。
}
SP2939 QTREE5 - Query on a tree V
维护树上染色联通块
- 每个颜色开 LCT 去维护
- 每一个 splay 维护一种颜色
access
,操作是要求虚链的个数,那么我们考虑改成树上差分,也就是类似 access
的地方,而询问操作似乎也是子树询问,那么搞一个 dfn
序和线段树维护一下。
SP16549 QTREE6 - Query on a tree VI
trick
就是我们将边权换成点权,意思就是对于子节点的颜色赋给父边,然后再连边,维护子树大小。
SP16580 QTREE7 - Query on a tree VII
multiset
去维护。
特殊题型
split
,直接查就可以了。
- 维护子树和,这个随便搞一搞
- 加入/删除点对,那就是修改权值,转到根上修改一下
- 查询,split 一下,然后判断子树和是否满足条件即可
access(x)
,然后 access(y)
并且记录在最后一个(根所在)的平衡树所相遇的节点,这个点就是 LCA 。因为这个题目不能破坏树的结构,所以我们只能通过树上差分的形式来求距离,也就是我们需要知道节点到根节点的距离,也就是要记录每一个平衡树的子树大小。
不会,先咕着...
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】