思想
对于每个节点,把所有子节点中子树最大的一个,成为重点,其它成为轻点。重点到父亲节点的连线成为重边,重边连接成若干条重链,其余的每个点称为重链。
可以发现,如果路径经过一条轻边,那么现在的子树大小至少缩小一半,所以每条路径可以被拆分成最多 \(\log n\) 条链,这样一来,就可以把较难维护的树形结构,改变成若干条链,并且可以发现按照先重后轻的原则遍历时,属于相同重链或相同子树的遍历序是连续的,就可以根据这个性质用线段树维护。
每个链在线段树上的区间是 \([dfn(top(u)),dfn(u)]\),每个子树在线段树上的区间是 \([dfn(u),dfn(u)+siz(u)-1]\),根据这个进行线段树操作。
模板
Code
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<queue>
#include<stack>
#include<vector>
#include<map>
using namespace std;
#define re register
typedef int ll;
typedef double db;
typedef unsigned long long ull;
const int maxn=1e6+10;
const int maxm=1e6+10;
const ll mod=1e9+7;
const ull base1=19260817;
const ull base2=19660813;
const ll maxxn=0x7ffffff;
const ll minxn=-0x7ffffff;
const db inf=1e13;
#define mid ((l+r)>>1)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define len (r-l+1)
inline ll read(){
ll x=0,w=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
while(c<='9'&&c>='0'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return x*w;
}
inline void write(ll x){
if(x<0){putchar('-');write(-x);return;}
if(x>=10) write(x/10);
putchar(x%10+'0');
}
ll n,m,root,p;
struct edge{
ll to,nxt;
}e[maxn];
ll cnt,head[maxn];
ll w[maxn],dfnw[maxn];
ll fa[maxn],son[maxn],dep[maxn],siz[maxn];
ll dfn[maxn],top[maxn],dfncnt;
inline void add_edge(ll u,ll v){
e[++cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
//第一次dfs遍历深度,父亲,子树大小和重儿子
inline void dfs1(ll u,ll f,ll d){
dep[u]=d,fa[u]=f,siz[u]=1;
ll maxson=-1;
for(int i=head[u];i;i=e[i].nxt){
ll v=e[i].to;
if(v==f) continue;
dfs1(v,u,d+1);
siz[u]+=siz[v];
if(siz[v]>maxson){
son[u]=v;
maxson=siz[v];
}
}
}
//第二次dfs遍历重链的顶点和dfs序
inline void dfs2(ll u,ll t){
dfn[u]=++dfncnt,dfnw[dfncnt]=w[u],top[u]=t;
if(!son[u]) return;
dfs2(son[u],t);//先遍历重儿子保证dfs序连续
for(int i=head[u];i;i=e[i].nxt){
ll v=e[i].to;
if(v==fa[u]||v==son[u]) continue;
dfs2(v,v);//轻儿子是所在重链的顶点
}
}
ll num[maxn],laz[maxn];
//线段树维护
struct XDS{
inline void build(ll rt,ll l,ll r){
if(l==r){
num[rt]=dfnw[l]%p;
return;
}
build(lson),build(rson);
num[rt]=(num[rt<<1]+num[rt<<1|1])%p;
}
inline void push_down(ll rt,ll l,ll r){
laz[rt<<1]+=laz[rt],laz[rt<<1|1]+=laz[rt];
num[rt<<1]+=laz[rt]*(mid-l+1),num[rt<<1|1]+=laz[rt]*(r-mid);
num[rt<<1]%=p,num[rt<<1|1]%=p,laz[rt]=0;
}
inline void update(ll rt,ll l,ll r,ll pl,ll pr,ll k){
if(pl<=l&&r<=pr){
laz[rt]+=k;
num[rt]+=k*len;
}
else{
if(laz[rt]) push_down(rt,l,r);
if(pl<=mid) update(lson,pl,pr,k);
if(pr>mid) update(rson,pl,pr,k);
num[rt]=(num[rt<<1]+num[rt<<1|1])%p;
}
}
inline ll query(ll rt,ll l,ll r,ll pl,ll pr){
if(pl<=l&&r<=pr){
return num[rt]%p;
}
else{
if(laz[rt]) push_down(rt,l,r);
ll sum=0;
if(pl<=mid) sum+=query(lson,pl,pr);
if(pr>mid) sum+=query(rson,pl,pr);
return sum;
}
}
}tree;
//处理区间时,因为一定在一条重链向下延伸的重链中,所以一直向上跳深度较大的一点直到lca
inline void update_pt(ll x,ll y,ll k){
k%=p;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
tree.update(1,1,n,dfn[top[x]],dfn[x],k);
x=fa[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);
tree.update(1,1,n,dfn[x],dfn[y],k);
}
inline ll query_pt(ll x,ll y){
ll sum=0;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
sum=(sum+tree.query(1,1,n,dfn[top[x]],dfn[x]))%p;
x=fa[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);
sum=(sum+tree.query(1,1,n,dfn[x],dfn[y]))%p;
return sum%p;
}
//处理子树时,因为dfs序一定是连续的,所以与线段树区间处理相同
inline void update_tr(ll x,ll k){
tree.update(1,1,n,dfn[x],dfn[x]+siz[x]-1,k);
}
inline ll query_tr(ll x){
return tree.query(1,1,n,dfn[x],dfn[x]+siz[x]-1)%p;
}
int main(){
n=read(),m=read(),root=read(),p=read();
for(int i=1;i<=n;i++){
w[i]=read();
}
for(int i=1;i<n;i++){
ll u=read(),v=read();
add_edge(u,v),add_edge(v,u);
}
dfs1(root,0,1);
dfs2(root,root);
tree.build(1,1,n);
while(m--){
ll opt=read();
if(opt==1){
ll x=read(),y=read(),k=read();
update_pt(x,y,k);
}
else if(opt==2){
ll x=read(),y=read();
printf("%d\n",query_pt(x,y)%p);
}
else if(opt==3){
ll x=read(),k=read();
update_tr(x,k);
}
else{
ll x=read();
printf("%d\n",query_tr(x)%p);
}
}
}
例题
1.P2690 ZJOI2008 书的统计
甚至比模板还要简单,没有复杂的转换,只需要维护区间和以及区间最大值,直接树剖套线段树。
代码鸽了。
2.P2486 SDOI2011 染色
一道很有趣的题。
因为要维护连续颜色段数,线段树的查询和修改就略有不同,函数参数应为:当前下标,当前区间,当前查询区间,要求查询区间,这样能够维护出边界的颜色,来特判左右两段是否合并为一个。
ACcode
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<queue>
#include<stack>
#include<vector>
#include<map>
using namespace std;
#define re register
typedef int ll;
typedef double db;
typedef unsigned long long ull;
const int maxn=1e6+10;
const int maxm=1e6+10;
const ll mod=1e9+7;
const ull base1=19260817;
const ull base2=19660813;
const ll maxxn=0x7ffffff;
const ll minxn=-0x7ffffff;
const db inf=1e13;
#define mid ((l+r)>>1)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define len (r-l+1)
inline ll read(){
ll x=0,w=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
while(c<='9'&&c>='0'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return x*w;
}
inline void write(ll x){
if(x<0){putchar('-');write(-x);return;}
if(x>=10) write(x/10);
putchar(x%10+'0');
}
ll n,m;
struct edge{
ll to,nxt;
}e[maxn];
ll cnt,head[maxn];
inline void add_edge(ll u,ll v){
e[++cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
ll w[maxn];
ll fa[maxn],son[maxn],dep[maxn],siz[maxn];
ll dfn[maxn],top[maxn],dfncnt;
inline void dfs1(ll u,ll f,ll d){
dep[u]=d,fa[u]=f,siz[u]=1;
ll maxson=-1;
for(int i=head[u];i;i=e[i].nxt){
ll v=e[i].to;
if(v==f) continue;
dfs1(v,u,d+1);
siz[u]+=siz[v];
if(siz[v]>maxson){
son[u]=v;
maxson=siz[v];
}
}
}
inline void dfs2(ll u,ll t){
dfn[u]=++dfncnt,top[u]=t;
if(!son[u]) return;
dfs2(son[u],t);
for(int i=head[u];i;i=e[i].nxt){
ll v=e[i].to;
if(v==fa[u]||v==son[u]) continue;
dfs2(v,v);
}
}
ll num[maxn],laz[maxn],lc[maxn],rc[maxn];
ll cl,cr;
struct XDS{
inline void push_down(ll rt){
if(laz[rt]){
laz[rt<<1]=laz[rt<<1|1]=laz[rt];
num[rt<<1]=num[rt<<1|1]=1;
lc[rt<<1]=rc[rt<<1]=lc[rt];
lc[rt<<1|1]=rc[rt<<1|1]=lc[rt];
laz[rt]=0;
}
}
inline void push_up(ll rt){
lc[rt]=lc[rt<<1],rc[rt]=rc[rt<<1|1];
ll ans=num[rt<<1]+num[rt<<1|1];
if(rc[rt<<1]==lc[rt<<1|1]) ans--;
num[rt]=ans;
}
inline void build(ll rt,ll l,ll r){
num[rt]=0;
if(l==r){
return;
}
build(lson),build(rson);
}
inline void update(ll rt,ll l,ll r,ll nl,ll nr,ll k){
if(nl==l&&r==nr){
num[rt]=laz[rt]=1;
lc[rt]=rc[rt]=k;
return;
}
push_down(rt);
if(nr<=mid) update(lson,nl,nr,k);
else if(nl>mid) update(rson,nl,nr,k);
else{
update(lson,nl,mid,k),update(rson,mid+1,nr,k);
}
push_up(rt);
}
inline ll query(ll rt,ll l,ll r,ll nl,ll nr,ll pl,ll pr){
if(l==pl) cl=lc[rt];
if(r==pr) cr=rc[rt];
if(nl==l&&r==nr){
return num[rt];
}
push_down(rt);
if(pr<=mid) return query(lson,nl,nr,pl,pr);
else if(pl>mid) return query(rson,nl,nr,pl,pr);
else{
ll ans=query(lson,nl,mid,pl,pr)+query(rson,mid+1,nr,pl,pr);
if(rc[rt<<1]==lc[rt<<1|1]) ans--;
return ans;
}
push_up(rt);
}
}tree;
inline void update_pt(ll u,ll v,ll k){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
tree.update(1,1,n,dfn[top[u]],dfn[u],k);
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
tree.update(1,1,n,dfn[u],dfn[v],k);
}
inline ll query_pt(ll u,ll v){
ll ansl=-1,ansr=-1;
ll ans=0;
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v),swap(ansl,ansr);
ans+=tree.query(1,1,n,dfn[top[u]],dfn[u],dfn[top[u]],dfn[u]);
if(cr==ansl) ans--;
ansl=cl;
u=fa[top[u]];
}
if(dep[u]<dep[v]) swap(u,v),swap(ansl,ansr);
ans+=tree.query(1,1,n,dfn[v],dfn[u],dfn[v],dfn[u]);
if(cr==ansl) ans--;
if(cl==ansr) ans--;
return ans;
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++){
w[i]=read();
}
for(int i=1;i<n;i++){
ll u=read(),v=read();
add_edge(u,v),add_edge(v,u);
}
dfs1(1,0,1);
dfs2(1,1);
tree.build(1,1,n);
for(int i=1;i<=n;i++){
tree.update(1,1,n,dfn[i],dfn[i],w[i]);
}
while(m--){
char opt;
cin>>opt;
if(opt=='C'){
ll u=read(),v=read(),k=read();
update_pt(u,v,k);
}
else{
ll u=read(),v=read();
printf("%d\n",query_pt(u,v));
}
}
}
3.P2590 ZJOI2008 树的统计
模板题,代码不贴了。
4.P1967 货车运输
\(\operatorname{Kruskal}\)最大生成树重构一下,可以放到树剖里跑,因为维护的是树上边权最小值,可以把边权挂在子节点上,处理在同一重链的 \([L,R]\) 的路径时,线段树查询 \([L+1,R]\) 的最小值即可。
两个\(\operatorname{dfs}\)
ll fa[maxn],son[maxn],dep[maxn],siz[maxn];
ll dfn[maxn],top[maxn],tow[maxn],dfnw[maxn],dfncnt;
inline void dfs1(ll u,ll f,ll d){
dep[u]=d,fa[u]=f,siz[u]=1;
ll maxson=-1;
for(int i=head[u];i;i=e[i].nxt){
ll v=e[i].to;
if(v==f) continue;
tow[v]=e[i].w;
dfs1(v,u,d+1);
siz[u]+=siz[v];
if(siz[v]>maxson){
son[u]=v;
maxson=siz[v];
}
}
}
inline void dfs2(ll u,ll t){
dfn[u]=++dfncnt,dfnw[dfncnt]=tow[u],top[u]=t;
if(!son[u]) return;
dfs2(son[u],t);
for(int i=head[u];i;i=e[i].nxt){
ll v=e[i].to;
if(v==fa[u]||v==son[u]) continue;
dfs2(v,v);
}
}
查询操作
inline ll query_ptmin(ll u,ll v){
ll ans=maxxn;
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
ans=min(ans,tree.query_min(1,1,n,dfn[top[u]],dfn[u]));
u=fa[top[u]];
}
if(dep[u]<dep[v]) swap(u,v);
ans=min(ans,tree.query_min(1,1,n,dfn[v]+1,dfn[u]));
return ans;
}
5.P3979 遥远的国度
换根的树剖,支持区间修改和区间维护最小值。
对于换根,因为树的性质不变,所以如果在初始化以 \(1\) 为根的树中,现在根节点和查询节点同属一颗子树的同左或同右的子树,就直接查询这个子树,如果在异侧那就找到子树的根,然后查询两次。
ACcode
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<queue>
#include<stack>
#include<vector>
#include<map>
using namespace std;
#define re register
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int maxn=1e5+10;
const int maxm=4e5+10;
const ll mod=1e9+7;
const ull base1=19260817;
const ull base2=19660813;
const ll maxxn=0x7fffffff;
const ll minxn=-0x7fffffff;
const db inf=1e13;
#define mid ((l+r)>>1)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define len (r-l+1)
inline ll read(){
ll x=0,w=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
while(c<='9'&&c>='0'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return x*w;
}
inline void write(ll x){
if(x<0){putchar('-');write(-x);return;}
if(x>=10) write(x/10);
putchar(x%10+'0');
}
ll n,m,root;
struct edge{
ll to,nxt;
}e[maxm];
ll head[maxn],cnt;
inline void add_edge(ll u,ll v){
e[++cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
ll w[maxn];
ll fa[maxn],son[maxn],dep[maxn],siz[maxn];
ll dfn[maxn],top[maxn],dfncnt,dfnw[maxn];
inline void dfs1(ll u,ll f,ll d){
fa[u]=f,dep[u]=d,siz[u]=1;
ll maxson=-1;
for(int i=head[u];i;i=e[i].nxt){
ll v=e[i].to;
if(v==f) continue;
dfs1(v,u,d+1);
siz[u]+=siz[v];
if(siz[v]>maxson){
maxson=siz[v];
son[u]=v;
}
}
}
inline void dfs2(ll u,ll t){
dfn[u]=++dfncnt,dfnw[dfncnt]=w[u],top[u]=t;
if(!son[u]) return;
dfs2(son[u],t);
for(int i=head[u];i;i=e[i].nxt){
ll v=e[i].to;
if(v==fa[u]||v==son[u]) continue;
dfs2(v,v);
}
}
ll minx[maxm],laz[maxm];
struct XDS{
inline void push_up(ll rt){
minx[rt]=min(minx[rt<<1],minx[rt<<1|1]);
}
inline void build(ll rt,ll l,ll r){
if(l==r){
minx[rt]=dfnw[l];
return;
}
build(lson),build(rson);
push_up(rt);
}
inline void push_down(ll rt){
if(laz[rt]){
laz[rt<<1]=laz[rt<<1|1]=laz[rt];
minx[rt<<1]=minx[rt<<1|1]=laz[rt];
laz[rt]=0;
}
}
inline void updatex(ll rt,ll l,ll r,ll ql,ll qr,ll k){
if(ql<=l&&r<=qr){
minx[rt]=laz[rt]=k;
return;
}
push_down(rt);
if(ql<=mid) updatex(lson,ql,qr,k);
if(qr>mid) updatex(rson,ql,qr,k);
push_up(rt);
}
inline ll queryx(ll rt,ll l,ll r,ll ql,ll qr){
if(ql<=l&&r<=qr){
return minx[rt];
}
ll ans=maxxn;
push_down(rt);
if(ql<=mid) ans=min(ans,queryx(lson,ql,qr));
if(qr>mid) ans=min(ans,queryx(rson,ql,qr));
push_up(rt);
return ans;
}
}tree;
inline void update_pt(ll u,ll v,ll k){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
tree.updatex(1,1,n,dfn[top[u]],dfn[u],k);
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
tree.updatex(1,1,n,dfn[u],dfn[v],k);
}
inline ll find(ll u){
if(u==root) return -1;
if(dfn[u]>=dfn[root]||dfn[u]+siz[u]-1<dfn[root]) return 0;
ll tmp=root;
while(top[tmp]!=top[u]){
if(fa[top[tmp]]==u) return top[tmp];
tmp=fa[top[tmp]];
}
return son[u];
}
inline ll query_tr(ll u){
ll ty=find(u);
if(ty==-1) return minx[1];
if(!ty) return tree.queryx(1,1,n,dfn[u],dfn[u]+siz[u]-1);
else{
ll ans=tree.queryx(1,1,n,1,dfn[ty]-1);
if(dfn[ty]+siz[ty]-1!=n){
ans=min(ans,tree.queryx(1,1,n,dfn[ty]+siz[ty],n));
}
return ans;
}
}
int main(){
n=read(),m=read();
for(int i=1;i<n;i++){
ll u=read(),v=read();
add_edge(u,v),add_edge(v,u);
}
for(int i=1;i<=n;i++){
w[i]=read();
}
dfs1(1,0,1);
dfs2(1,1);
tree.build(1,1,n);
root=read();
for(int i=1;i<=m;i++){
ll opt=read();
if(opt==1) root=read();
else if(opt==2){
ll u=read(),v=read(),k=read();
update_pt(u,v,k);
}
else{
ll u=read();
printf("%lld\n",query_tr(u));
}
}
return 0;
}
5.P1505 国家集训队 旅游
多用懒标记维护几个东西就ok了,因为要维护最大值以及最小值,所以取负的时候直接交换最大最小值。
放个上传和下传代码
inline void push_up(ll rt){
maxx[rt]=max(maxx[rt<<1],maxx[rt<<1|1]);
minx[rt]=min(minx[rt<<1],minx[rt<<1|1]);
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
inline void push_down(ll rt){
laz[rt<<1]^=1,laz[rt<<1|1]^=1;
maxx[rt<<1]=-maxx[rt<<1],maxx[rt<<1|1]=-maxx[rt<<1|1];
minx[rt<<1]=-minx[rt<<1],minx[rt<<1|1]=-minx[rt<<1|1];
sum[rt<<1]=-sum[rt<<1],sum[rt<<1|1]=-sum[rt<<1|1];
swap(maxx[rt<<1],minx[rt<<1]);
swap(maxx[rt<<1|1],minx[rt<<1|1]);
laz[rt]=0;
}
6.P3258 JLOI2014 松鼠的新家
区间修改+单点查询
注意每次走的终点要减\(1\),因为会重复。
7.P2146 NOI2015 软件包管理器
- 安装操作:把根节点到当前节点路径赋值为\(1\)
- 卸载操作:当前节点子树赋值为\(0\)
输出变化量。