树链剖分(线段树)
树链剖分是将一棵树分成几条链,把树形变为线性以减少处理难度。
对于轻重链剖分,每个非叶子节点的儿子中儿子数量最多的那个儿子为重儿子,其余所有儿子为轻儿子。
重边指连接两个重儿子的边,其余的边为轻边。
重链指相邻重边连起来的一条重儿子的链。
每一条重链以轻儿子为起点。
dfs序中子树的dfs序是连续的,所以对于一棵子树来说,在线段树上是一段连续的区间,但是从根到x不一定是连续的,所以对于从根节点开始的修改或查询,不可以直接在线段树上进行操作。
struct HeavyLightDecomposition{
struct tree{
int fa,sz,dep,son,top,ipt,val;
}t[N];
int dfn,root=1,inf=1e9,mp[N];
#define f(p) (t[p].fa)
#define sz(p) (t[p].sz)
#define d(p) (t[p].dep)
#define son(p) (t[p].son)
#define t(p) (t[p].top)
#define i(p) (t[p].ipt)
#define v(p) (t[p].val)
void find(int x,int fa){
f(x)=fa;/*父亲*/
sz(x)=1;/*子树大小*/
d(x)=d(fa)+1;/*深度*/
son(x)=0;/*重儿子编号*/
for(int i=h[x];i;i=e[i].next){
int y=e[i].to;
if(y==fa)continue;
find(y,x);
sz(x)+=sz(y);/*累加子树大小*/
if(sz(son(x))<sz(y))son(x)=y;/*更新x的重儿子*/
}
}
void connect(int x,int tp){
i(x)=++dfn;/*dfs序在第二次dfs时统计*/
t(x)=tp;/*该点所在链的顶端*/
// mp[dfn]=x;/*映射dfs序*/
mp[dfn]=v(x);/*为了方便线段树传参,直接映射的是x点的权值*/
if(son(x))connect(son(x),tp);/*优先连接重儿子,每条重链的dfs序时连续的*/
for(int i=h[x];i;i=e[i].next){
int y=e[i].to;
if(y==son(x)||y==f(x))continue;
connect(y,y);/*每个轻儿子有一条从自己开始的链*/
}
}
inline void build(int l,int r){
find(root,0);
connect(root,root);/*首先根节点这条链的链顶肯定是根节点*/
seg.build(1,l,r,mp);
}
inline int LCA(int x,int y){
while(t(x)!=t(y)){/*两个点不在同一条链上时*/
if(d(t(x))>d(t(y)))x=f(t(x));/*深度小的跳到链顶的父亲*/
else y=f(t(y));
}
return d(x)<d(y)?x:y;/*在同一条链上后深度小的就是LCA*/
}
inline void modify(int x,int y,int v){
while(t(x)!=t(y)){/*若不在同一条链*/
if(d(t(x))<d(t(y)))swap(x,y);/*钦定x所在链的链顶较深*/
seg.modify(1,i(t(x)),i(x),v);/*相应数据结构更新x的链顶到x的路径*/
x=f(t(x));/*x向上跳到链顶的父亲*/
}
if(d(x)>d(y))swap(x,y);/*在同一条链上后钦定x较浅,即x的dfs序小于y的dfs序*/
seg.modify(1,i(x),i(y),v);/*相应数据结构更新最后的x到y的一段区间*/
}
inline int query(int x,int y){
int re=0;
while(t(x)!=t(y)){
if(d(t(x))<d(t(y)))swap(x,y);
re+=seg.query(1,i(t(x)),i(x));
x=f(t(x));
}
if(d(x)>d(y))swap(x,y);
re+=seg.query(1,i(x),i(y));
return re;
}
inline void modifysubtree(int x,int v){
seg.modify(1,i(x),i(x)+sz(x)-1,v);
}
inline int querysubtree(int x){
return seg.query(1,i(x),i(x)+sz(x)-1);
}
}hld;
对于边权而言,需要将边权转化成点权,选择深度较深的点的点权为这条边的边权。当x和y在同一条链上时,假设x的深度小,则x为LCA,如果直接修改或查询ipt[x]到ipt[y],LCA的父亲也会被计入,但是不应该被计入,所以可以直接从ipt[x]+1到ipt[y],这样就避免了LCA的处理。
void find(int x,int fa){
f(x)=fa;/*父亲*/
sz(x)=1;/*子树大小*/
d(x)=d(fa)+1;/*深度*/
son(x)=0;/*重儿子编号*/
for(int i=h[x];i;i=e[i].next){
int y=e[i].to;
if(y==fa)continue;;
v(y)=e[i].w;
find(y,x);
sz(x)+=sz(y);/*累加子树大小*/
if(sz(son(x))<sz(y))son(x)=y;/*更新x的重儿子*/
}
}
inline void modify(int x,int y,int v){
while(t(x)!=t(y)){/*若不在同一条链*/
if(d(t(x))<d(t(y)))swap(x,y);/*钦定x所在链的链顶较深*/
seg.modify(1,i(t(x)),i(x),v);/*相应数据结构更新x的链顶到x的路径*/
x=f(t(x));/*x向上跳到链顶的父亲*/
}
if(d(x)>d(y))swap(x,y);/*在同一条链上后钦定x较浅,即x的dfs序小于y的dfs序*/
seg.modify(1,i(x)+1,i(y),v);/*相应数据结构更新最后的x到y的一段区间*/
}
inline int query(int x,int y){
int re=0;
while(t(x)!=t(y)){
if(d(t(x))<d(t(y)))swap(x,y);
re+=seg.query(1,i(t(x)),i(x));
x=f(t(x));
}
if(d(x)>d(y))swap(x,y);
re+=seg.query(1,i(x)+1,i(y));
return re;
}
树上黑白点,可以单点颜色翻转,查询1到x路径上第一个黑点,初始全白。树剖思想,对每条重链维护一个set,dfs序就是权值,每次向上跳链顶的父亲即可。
inline void modify(int x){
dark[x]^=1;
if(dark[x])ans[t(x)].insert(i(x));
else ans[t(x)].erase(i(x));
}
inline int query(int x){
int re=inf;
while(x){
if(ans[t(x)].empty()){
x=f(t(x));
continue;
}
int y=*ans[t(x)].begin();
if(d(mp[y])<=d(x))re=mp[y];
x=f(t(x));
}
return re==inf?-1:re;
}
一棵树给出起点,每次给出目标点和限制步数,在步数限制内尽量走到目标点处,无法到达则走到最近的点,下次从该点出发,每次输出到达了哪个点。首先关于到LCA的距离进行分类讨论确定在七点到LCA的链上还是LCA到终点的链上,之后通过暴力跳重链。
inline int move(int x,int k){
while(d(x)-d(t(x))+1<=k)k-=d(x)-d(t(x))+1,x=f(t(x));
return mp[i(x)-k];
}
inline int solve(int x,int y){
int z=LCA(m,x);
if(d(m)-d(z)==y)return m=z;
else if(d(m)+d(x)-2*d(z)<=y)return m=x;
else if(d(m)-d(z)>y)return m=move(m,y);
else return m=move(x,d(x)-d(z)-(y-(d(m)-d(z))));
}
支持五种操作,修改边权,边权取反,询问边权和,询问边权最值。对于取反操作可以全乘-1,之后再交换max和min即可。
inline void pushdown(int p){
if(!n(p))return;
n(p)=0;
n(lc)^=1;
n(rc)^=1;
s(lc)*=-1;
s(rc)*=-1;
ma(lc)*=-1;
ma(rc)*=-1;
mi(lc)*=-1;
mi(rc)*=-1;
swap(ma(lc),mi(lc));
swap(ma(rc),mi(rc));
}
void negate(int p,int l,int r){
if(l<=l(p)&&r(p)<=r){
n(p)^=1;
s(p)*=-1;
ma(p)*=-1;
mi(p)*=-1;
swap(ma(p),mi(p));
return;
}
pushdown(p);
int mid=l(p)+r(p)>>1;
if(l<=mid)negate(lc,l,r);
if(mid<r)negate(rc,l,r);
pushup(p);
}
维护一个序列区间内的连续颜色段数量。增加标记区间两端点的颜色和区间整个覆盖的标记,线段树合并区间时,若左儿子的右端点颜色和右儿子的左端点颜色相同则答案减一。在跳重链时,需要记录当前要跳的路径的上一次左端点颜色和另一条路径的上一次左端点颜色,若当前右端点颜色和当前路径上次左端点颜色相同则答案减一,在交换x和y时也要交换两条路径的颜色,当x和y在同一条重链后,两边端点颜色都要比较。
inline void pushup(int p){
st(p)=st(lc);
ed(p)=ed(rc);
s(p)=s(lc)+s(rc);
if(ed(lc)==st(rc))s(p)--;
}
inline void pushdown(int p){
if(!t(p))return;
t(lc)=t(rc)=t(p);
s(lc)=s(rc)=1;
st(lc)=ed(lc)=st(p);
st(rc)=ed(rc)=ed(p);
t(p)=0;
}
void build(int p,int l,int r,int a[]){
t[p]={l,r,0,0,0,0};
if(l==r)return s(p)=1,void();
int mid=l+r>>1;
build(lc,l,mid,a);
build(rc,mid+1,r,a);
pushup(p);
}
void cover(int p,int l,int r,int x){
if(l>r(p)||r<l(p))return;
if(l<=l(p)&&r(p)<=r){
s(p)=t(p)=1;
st(p)=ed(p)=x;
return;
}
pushdown(p);
int mid=l(p)+r(p)>>1;
if(l<=mid)cover(lc,l,r,x);
if(mid<r)cover(rc,l,r,x);
pushup(p);
}
int query(int p,int l,int r){
if(l<=l(p)&&r(p)<=r){
if(l(p)==l)st=st(p);
if(r(p)==r)ed=ed(p);
return s(p);
}
pushdown(p);
int mid=l(p)+r(p)>>1;
if(r<=mid)return query(lc,l,r);
if(mid<l)return query(rc,l,r);
int re=query(lc,l,r)+query(rc,l,r);
re-=ed(lc)==st(rc);
return re;
}
inline int query(int x,int y){
int a=-1,b=-1,re=0;
while(t(x)!=t(y)){
if(d(t(x))<d(t(y)))swap(x,y),swap(a,b);
re+=seg.query(1,i(t(x)),i(x));
re-=ed==a;
a=st;
x=f(t(x));
}
if(d(x)>d(y))swap(x,y),swap(a,b);
re+=seg.query(1,i(x),i(y));
re-=(st==a)+(ed==b);
return re;
}

浙公网安备 33010602011771号