上午刚打了一个树链剖分的模版题……
HDU:3966 Aragorn’s Story
题意:(多组数据)
给定一棵带权值树(节点数n<=50000,操作数m<=100000),支持2种操作:
1.查询某个点的权值;
2.对两个点路径的所有权值同时加或减一个数;
考虑到一次查询的正常复杂度O(n),一次更改的复杂度O(n),可知O(nm)超时。
O(mlog2n)可过。
考虑用一些数据结构,但是显然这里线段树什么的是不合理的,因为它不是一条链。
但是我们可以把一棵树砍成几条链,连起来组成一条长链。
这就是树链剖分的基本原理。
剖的方法:
轻重边划分:
size[x]定义为以x为子树的节点个数。
重儿子:当前节点下size值最大的儿子(若有size相等则随便连一个)。
重边:连接当前节点和其重孩子的边。
轻边:连接当前节点和除了重孩子的边。
需要使用到的变量有:
tim//时间戳
size[]//当前节点的size数
son[]//记录当前节点的重儿子
fa[]//记录当前节点的父亲
dep[]//当前节点的深度
top[]//当前重链的最顶部(距离根节点最近的点)
tid[]//当前节点的时间
Rank[]//当前时间对应着哪个节点
在树链剖分时,首先我们要确定每一个节点的size从而确定它的重儿子,然后连成重边。
之后我们再把重边都连起来拉成重链。
这个过程需要两边dfs。
void dfs1(int rt,int f,int depth){//确定重儿子
fa[rt]=f;//记录父亲
dep[rt]=depth;//记录深度
size[rt]=1;//size初始化为1(即节点自身)
for(int i=head[rt];~i;i=edge[i].next){//遍历邻接表
int v=edge[i].to;
if(v!=f){//避免指回父亲
dfs1(v,rt,depth+1);
size[rt]+=size[v];//统计当前节点从孩子能获得的size
if(son[rt]==-1||size[son[rt]]<size[v])//如果没有重孩子||size值大于原来重孩子
son[rt]=v;
}
}
}
void dfs2(int rt,int tp){//tp代表顶端
top[rt]=tp;
tid[rt]=++tim;
Rank[tim]=rt;
if(son[rt]==-1)return;
dfs2(son[rt],tp);//同条重链上,顶点相同
for(int i=head[rt];~i;i=edge[i].next){
int v=edge[i].to;
if(v!=fa[rt]&&v!=son[rt])
dfs2(v,v);
}
}
tid&&Rank——个人认为最不好懂的树链剖分部分
树链剖分是按照时间来建造整棵树的,也就意味着,当我们想要找一个节点在当前链上的位置时,
tid数组起到了至关重要的作用,因为tid[x]=tim,而tim对应的就是当前的链上的位置。
而Rank数组则是在当前时刻对应的节点。
所以有:
x=Rank[tid[x]];
理解了这个之后,我们可以用tid来找出某个节点对应的整条链的位置,用Rank找出某个位置对应的树的哪个节点,这样,整棵树就彻彻底底可以转成链了。
但是有些东西在树链剖分中不能想当然,比如有个东西叫LCA……
一开始我天真地以为更改树上的两点间路径上的所有权值,那么就可以这样:
找出他们的LCA然后把一条路径分成两条链进行更改,用线段树进行维护。
现在想想……真是天真的很……
因为:
两遍dfs后形成的整条长链中的每个元素,或是短链或是单点,都来自于一整条重链或者是轻边。
而LCA……并不一定与那两个点形成一整条重链或者一条轻边(也就是,混搭)。
所以说……用LCA这个东西来进行区间统计亦或是区间修改就是在坑自己。(至少这道题是在坑自己)
区间修改的思路:
x,y之间的路径进行区间修改。
void chage(){
while(x,y不在一条重链上){
dep[top[x]]<dep[top[y]]?swap(x,y):1;
对x---top[x]所在的链进行修改;
x=fa[top[x]];
}
对x---y所在的链进行修改。
}
是时候该发本题的代码了……
本题应注意两点,第一点,多组数据,第二点,HDU的爆栈(请自行扩充栈)
扩充栈:#pragma comment(linker, “/STACK:1024000000,1024000000”)
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#define N 50005
#define lc rt<<1,l,mid
#define rc rt<<1|1,mid+1,r
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;
int n,m,p,cnt=0,tim=0;
int son[N],dep[N],Rank[N],size[N],tid[N],fa[N],head[N<<1],w[N],top[N],sum[N<<2],lazy[N<<2];
struct Edge{
int next,to;
}edge[N<<1];
void save(int u,int v){
edge[cnt].next=head[u];
edge[cnt].to=v;
head[u]=cnt++;
}
void dfs1(int rt,int f,int depth){
fa[rt]=f;
dep[rt]=depth;
size[rt]=1;
for(int i=head[rt];~i;i=edge[i].next){
int v=edge[i].to;
if(v!=f){
dfs1(v,rt,depth+1);
size[rt]+=size[v];
if(son[rt]==-1||size[son[rt]]<size[v])
son[rt]=v;
}
}
}
void dfs2(int rt,int tp){
top[rt]=tp;
tid[rt]=++tim;
Rank[tim]=rt;
if(son[rt]==-1)return;
dfs2(son[rt],tp);
for(int i=head[rt];~i;i=edge[i].next){
int v=edge[i].to;
if(v!=son[rt]&&v!=fa[rt])
dfs2(v,v);
}
}
//线段树部分
void build(int rt,int l,int r){
lazy[rt]=0;
if(l==r){
sum[rt]=w[Rank[l]];
return;
}
int mid=(l+r)>>1;
build(lc);
build(rc);
}
void pushdown(int rt){
if(lazy[rt]){
lazy[rt<<1]+=lazy[rt];
lazy[rt<<1|1]+=lazy[rt];
sum[rt<<1]+=lazy[rt];
sum[rt<<1|1]+=lazy[rt];
lazy[rt]=0;
}
}
void modify(int rt,int l,int r,int lm,int rm,int modi){
if(l>rm||r<lm)return;
if(l>=lm&&r<=rm){
lazy[rt]+=modi;
sum[rt]+=modi*(r-l+1);
return;
}
int mid=(l+r)>>1;
pushdown(rt);
if(mid>=lm)modify(lc,lm,rm,modi);
if(mid<rm)modify(rc,lm,rm,modi);
}
void Change(int x,int y,int val) //在剖好的链上进行的更改
{
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]]) swap(x,y);
modify(1,1,n,tid[top[x]],tid[x],val);
x=fa[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);
modify(1,1,n,tid[x],tid[y],val);
}
int query(int rt,int l,int r,int q){
if(l==r)return sum[rt];
int mid=(l+r)>>1;
pushdown(rt);
int ans=0;
if(mid>=q)ans=query(lc,q);
if(mid<q)ans=query(rc,q);
return ans;
}
int main (){
while(~scanf("%d%d%d",&n,&m,&p)){
memset(head,-1,sizeof(head));
memset(edge,0,sizeof(edge));
cnt=tim=0;
memset(son,-1,sizeof(son));
for(int i=1;i<=n;i++)
scanf("%d",&w[i]);
for(int i=1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
save(u,v);save(v,u);
}
dfs1(1,0,0);
dfs2(1,1);
build(1,1,n);
for(int i=1;i<=p;i++){
char c[5];
scanf("%s",c);
if(c[0]=='Q'){
int q;
scanf("%d",&q);
printf("%d\n",query(1,1,n,tid[q]));
}
else {
int c1,c2,cg;
scanf("%d%d%d",&c1,&c2,&cg);
if(c[0]=='D')
Change(c1,c2,-cg);
else
Change(c1,c2,cg);
}
}
}
return 0;
}
至此,一道简单的树链剖分模版题分析完毕!
请注意,”简单“一词修饰的是”模版题“,而不是树链剖分。(严肃脸)