熟练剖分详解见:https://blog.csdn.net/tangzhide123yy/article/details/77532880
熟练剖分一般有如下几个步骤:
1.dfs1()求出fa,deep,size,son
2.dfs2()求出top,p
3.add()/Query ()树链剖分核心部分
4.updata()线段树更新操作(如单点修改,区间修改)
5.query()线段树查询(如区间查询,单点查询)
inline void build(int l,int r,int rt){//建立一棵线段树
tr[rt].l=l,tr[rt].r=r;
if(l==r){tr[rt].sum=w[id[l]];return ;}
//w[]存储的为原始的点权,写为w[id[l]]是因为dfs时的顺序不一样
int midd=l+(r-l)/2;
build(l,midd,ls),build(midd+1,r,rs);
pushup(rt);
}
inline void dfs1(int x,int fat,int dep) {
//这里应该就是树链剖分的第一个操作,把树上每个节点的size,fa等统统搞出来
deep[x]=dep,fa[x]=fat,sz[x]=1;//fat表示x的爸爸
for(int i=0;i.size();i++){
int v=g[x][i];
if(v!=fat){
dfs1(v,x,dep+1);
sz[x]+=sz[v];
//son[x]记录x的儿子中的重点
}
}
}
inline void dfs2(int x,int tp){//这个操作应该就是处理轻链和重链
top[x]=tp;pos[x]=++cnt;id[pos[x]]=x;
//pos记录的是遍历到该点时的时间,id记录的是当时间为pos[x]时的编号是x,
if(son[x]==-1) return ;//到了叶子节点就退出。
dfs2(son[x],tp);//继续递归,并且是处理的重链和重点
for(int i=0;i.size();i++){
int v=g[x][i];
if(v!=fa[x]&&v!=son[x]) dfs2(v,v);//处理轻链
}//tp记录的是重点的祖先节点编号
}
inline void update(int l,int r,int c,int rt){//区间、单点修改
if(l<=tr[rt].l&&tr[rt].r<=r){
tr[rt].sum=(tr[rt].sum+(c*(tr[rt].r-tr[rt].l+1)%mod))%mod;
tr[rt].tag+=c%mod;
return ;
}
pushdown(rt);//下传tag
if(l<=mid) update(l,r,c,ls);
if(r>mid) update(l,r,c,rs);
pushup(rt);//跟新父亲
}
inline ll query(int l,int r,int rt){//区间查询
if(l<=tr[rt].l&&tr[rt].r<=r) return tr[rt].sum;
pushdown(rt);
int ans=0;
if(l<=mid) ans+=query(l,r,ls),ans%=mod;
if(r>mid) ans+=query(l,r,rs),ans%=mod;
return ans%mod;
}
inline ll add(int t1,int t2,int c,int ok){//ok为一个标记,表示是进行哪一个操作
ll u=t1,v=t2,ans=0;
while(top[u]!=top[v]){//在不同链上的情况
if(deep[top[u]]>deep[top[v]]) swap(u,v);
if(!ok)update(pos[top[v]],pos[v],c,1);//这里是更新值,例如本题中的从a到b都加上某一个值
else ans+=query(pos[top[v]],pos[v],1),ans%=mod;
v=fa[top[v]];//继续操作下去,直到这两个点爬到他们的公共节点
}
if(deep[u]>deep[v]) swap(u,v);//处理在同一条链上的情况
if(!ok) update(pos[u],pos[v],c,1);
//与维护边权不同,不像维护边权那样要写成query(pos[son[u]],pos[v],1);
else ans+=query(pos[u],pos[v],1);
return ans%=mod;
}
void build(int l,int r,int rt) {
tree[rt].l=l;tree[rt].r=r;
if(l==r) {tree[rt].max=w[l];return;}
/*维护边权,至于为什么是w[l],因为我们在dfs2中有记录每条边是什么时候搜索到的,
并记录了下来,所以这里按照搜索时顺序放入线段树中*/
int midd=(l+(r-l)/2);
build(l,midd,ls);build(midd+1,r,rs);
pushup(rt);
}
void dfs1(int x,int fat,int dep) {//第一遍dfs处理出重儿子
deep[x]=dep,fa[x]=fat,sz[x]=1;
for(register int i=head[x];i;i=bot[i].nx) {
int v=bot[i].nd;
if(v!=fat) {
road[bot[i].id]=v;
//这里与维护点权不同,因为我们要把边加入进线段树中,所以要记录第几条边连向的点的编号
dfs1(v,x,dep+1);
sz[x]+=sz[v];
if(son[x]==0||sz[son[x]].co;
//这里也与维护点权不同,需要记录下重边的权值,方便dfs2()的进行
}
}
}
}
void dfs2(int x,int tp,int cost){
top[x]=tp;pos[x]=++tot;id[tot]=x;w[tot]=cost;//w记录边权的权值
if(son[x]==0) return ;
dfs2(son[x],tp,son_cost[x]);
for(register int i=head[x];i;i=bot[i].nx) {
int v=bot[i].nd;
if(v!=fa[x]&&v!=son[x]) dfs2(v,v,bot[i].co);
}
}
void updata(int rt,int l,int data) {
if(tree[rt].l==tree[rt].r) { tree[rt].max=data; return ;}
else {
if(l<=mid) updata(ls,l,data);
else updata(rs,l,data);
pushup(rt);
/*
这里跟维护点权不一样,是因为我们在线段树的第i个区间就是搜索时的第i条边,
所以查找时只要记录下l与mid的关系即可
*/
}
}
long long query(int l,int r,int rt) {
if(l<=tree[rt].l&&tree[rt].r<=r) return tree[rt].max;
long long ans=-0x3f3f3f3f;
if(l<=mid) ans=max(ans,query(l,r,ls));
if(r>mid) ans=max(ans,query(l,r,rs));
pushup(rt);
return ans;
}
long long Query(int t1, int t2,int data) {
long long ans=-0x3f3f3f3f,u=t1,v=t2;
if(t1==t2) return 0;
while(top[u]!=top[v]) {
if(deep[top[u]]>deep[top[v]]) swap(u,v);
ans=max(ans,query(pos[top[v]],pos[v],1));
v=fa[top[v]];
}
if(u==v) return ans;
if(deep[u]>deep[v]) swap(u,v);
ans=max(ans,query(pos[son[u]],pos[v],1));
//如果在一条重链或轻链上时,在处理点权时这样写query(pos[u],pos[v],1),
//可能与当初dfs1()时记录了road[bot[i].id]=v有关吧
//边权时这样写query(pos[son[u]],pos[v],1)
return ans;
}