昨天遇到了这样一道题目:
一个公司有 n 个员工,编号从 0 到 n-1 ,每个员工都有一个直系上司,编号为 0 的是整个公司的董事长(包工头),现在,给出每个员工每月的初始工资,为了鼓励最佳员工和最佳部门,现在,董事会会有以下两种询问:
1、employee x y z :询问员工 x 的工资,如果员工的 x 的工资小于 y ,那么就给他(她)涨 z 元
2、department x y z :询问员工 x 领导的部门的平均工资,如果平均工资小于 y ,那么就给这个部门的每个员工涨 z 元
当时一看到题目就知道是树链剖分,对于树链剖分我只知道个大概,队友给我讲了他的做法,但是没有听明白,最后还是没有在比赛期间做出来,感觉甚是可惜,深感以后决不能再因为这样挂题,于是昨天晚上还有今天一直在恶补树链剖分,只是搞懂了一些简单的问题,现在总结一下。
这样的题目,只是涉及到节点和子树的问题,并不涉及到对树上的路径进行操作,显得要简单一点。
为了深刻的理解树链剖分,我们的首先来看一下树的遍历,这里指的是DFS
在遍历的时候,假定我们给每一个遍历的节点一个从小到大的编号,这样的编号有什么特点呢?我们来看一下:
现在假定我们有上面一颗树:整棵树的根是 1 ,我们从根节点遍历,遍历的方式利用 先根遍历 ,1 节点的编号是 1 ,然后遍历的时候遇到一个新的节点,序号就 +1,这样,我们不难得到以下的序号:
节点 :1 2 4 5 8 9 6 3 7
节点序号:1 2 3 4 5 6 7 8 9
得到这个表格有什么用呢?
我们仔细观察一下:
节点 1 是整棵树的根,节点 1 的序号是整棵树的节点里面序号最小的,并且,以节点 1 的为根的数的节点的序号全部是紧接着着节点 1 的序号的
节点 2 是子树(2 4 5 6 8 9)的根,节点 2 的序号是以节点 2 为根的树中节点序号最小的,并且,以节点 2 为根的子树的节点的序号全部是紧接着节点 2 的
节点 5 是子树(5 8 9)的根,节点 5 的序号是以节点 5 为根的树中节点序号最小的,并且,以节点 5 为根的子树的节点的序号全部是紧接着节点 5 的
。。。。。。
我们发现了这样的一条规律,以节点 u 为根的子树 T,T 中所有的节点的序号一定是大于节点 u 的序号的,并且,我们得到的 DFS序列中,T 中所有的节点一定是紧接着 U 出现的
当然,这只是一个小小的规律,直觉强的觉得不用证明,肯定是这样的,但既然提出了这个规律,我还是简单的证明一下这个规律是正确的吧
1、证明 T 中所有节点的序号大于 U
我们在遍历 T 中所有节点的时候,遍历的方式是先根后子树,那么,那么我们一定是先遍历的节点 U ,然后再遍历以节点 U 为根的子树 T 中的所有节点,这样,节点 U 的序号一定是子树中所有节点的序号中最小的
2、证明 T 中的所有节点在生成的DFS序列中一定是紧接着节点 U 的出现的
证明这个问题,其实就跟上面的问题一样,同样因为我们是先根遍历的,那么作为根节点的 U 在DFS序列中一定是出现在 T 中所有节点的最前面,而中途不可能遍历到其他子树的节点,如果遍历到了,说明以 U 为根的子树已经遍历完了
得到了这样的性质,我们就可以把对整棵子树的操作改成对生成的 DFS 序列中的节点进行操作了,这就变成了简单的 线段树成段更新,成段查询问题了,而这道题我们只需要加一个预处理——得到 DFS 序列,同时得到 DFS 序列中,以某一节点为根的子树在DFS序列中的结束位置即可,我们可以用 low [u] 来记录生成的 DFS序列 中,以 U 节点为根的子树到哪里截止,不难想出如下地推方程:low[u] = max( low[u_son1] , low[u_son2] , ... low[u_sonk] )
下面给出 DFS 预处理过程:
之所以 low[v] = ind 是根据 DFS过程的性质,小小的利用一下而已。
这道题不知道队友是怎么做的,不过思路是一样的,但是昨天一直挂在按照原来的序号输出所有员工的工资上了,我加了个 id[u] 表示节点 u 在生成的 DFS 序列中的起始位置,这样就解决了按照原来序号输出的问题了
这里是原题连接:有兴趣的朋友可以去切一切,虽然题目很水。。 Money out of Thin Air
有什么细节还不明白的可以看看我的代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <string>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <list>
#define INT_INF 0x3fffffff
#define LL_INF 0x3fffffffffffffff
#define EPS 1e-12
#define MOD 1000000007
#define PI 3.141592653579798
#define N 50005
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef double DB;
struct data
{
int st,en;
LL val,bj;
} T[N*4];
int id[N] , low[N] , ind; //id[i] low[i] 分别表示 节点 i 在DFS序列中的位置以及以节点 i 为根的子树中的节点在DFS序列中最靠后的位置
LL a[N];
struct Edge //前向星存边
{
int en,next;
} edge[N];
int tot , head[N];
void add_edge(int st,int en) //加边
{
edge[tot].en=en;
edge[tot].next=head[st];
head[st]=tot++;
}
void dfs(int v)
{
id[v]=++ind; // ind 是全局变量,表示节点在DFS序列中的序号
for(int i=head[v];i!=-1;i=edge[i].next) //边是用前向星存储的
{
dfs(edge[i].en);
}
low[v]=ind;
}
void build(int v,int st,int en) //建树
{
T[v].st=st; T[v].en=en;
T[v].val=0; T[v].bj=0;
if(st==en) return;
int mid=(st+en)>>1;
build(v<<1,st,mid);
build(v<<1|1,mid+1,en);
}
void clear(int v) //传递标记
{
T[v<<1].bj+=T[v].bj;
T[v<<1|1].bj+=T[v].bj;
T[v<<1].val+=(T[v<<1].en-T[v<<1].st+1)*T[v].bj;
T[v<<1|1].val+=(T[v<<1|1].en-T[v<<1|1].st+1)*T[v].bj;
T[v].bj=0;
}
void add(int v,int st,int en,LL val) //成段更新
{
if(st<=T[v].st && T[v].en<=en)
{
T[v].val+=(T[v].en-T[v].st+1)*val;
T[v].bj+=val;
return;
}
if(T[v].bj!=0) clear(v);
int mid=(T[v].st+T[v].en)>>1;
if(en<=mid) add(v<<1,st,en,val);
else if(st>mid) add(v<<1|1,st,en,val);
else
{
add(v<<1,st,en,val);
add(v<<1|1,st,en,val);
}
T[v].val=T[v<<1].val+T[v<<1|1].val;
}
LL query(int v,int st,int en) // 成段查询
{
if(st<=T[v].st && T[v].en<=en) return T[v].val;
if(T[v].bj!=0) clear(v);
int mid=(T[v].st+T[v].en)>>1;
if(en<=mid) return query(v<<1,st,en);
else if(st>mid) return query(v<<1|1,st,en);
else return query(v<<1,st,en)+query(v<<1|1,st,en);
}
int main()
{
int n,q,fa;
scanf("%d%d%I64d",&n,&q,&a[0]);
memset(head,-1,sizeof(head));
tot=0;
for(int i=1;i<n;i++)
{
scanf("%d%I64d",&fa,&a[i]);
add_edge(fa,i);
}
memset(low,0,sizeof(low));
memset(id,0,sizeof(id));
ind=0; dfs(0); build(1,1,n);
for(int i=0;i<n;i++)
add(1,id[i],id[i],a[i]);
char s[20];
int v; LL val , add_val;
for(int i=1;i<=q;i++)
{
scanf("%s%d%I64d%I64d",s,&v,&val,&add_val);
if(strcmp(s,"employee")==0)
{
LL tot=query(1,id[v],id[v]);
if(tot<val) add(1,id[v],id[v],add_val);
}
else
{
LL tot=query(1,id[v],low[v]);
LL cmp=(LL)val*(low[v]-id[v]+1);
if(tot<cmp) add(1,id[v],low[v],add_val);
}
}
for(int i=0;i<n;i++)
printf("%I64d\n",query(1,id[i],id[i]));
return 0;
}
另外,还有一道题目,题目大意是这样的:有一个苹果树,开始的时候树中每个节点都有一个苹果,现在给一些操作:
1、C x :如果节点 x 有苹果就把这个节点的苹果摘掉,如果没有苹果,表示这个节点长出一个苹果
2、Q x :询问以节点 x 为根的子树中共有多少苹果
题目连接:Apple Tree
也是对树中的节点进行更新的题目,并且不涉及到树中的路径,我们还是可以按照上面的操作,把树型结构通过一遍先根遍历转换成线性结构,然后记录一个树状数组就行了,就不怎么详细说明解题方法了,直接上代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <string>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <list>
#define INT_INF 0x3fffffff
#define LL_INF 0x3fffffffffffffff
#define EPS 1e-12
#define MOD 1000000007
#define PI 3.141592653579798
#define N 100005
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef double DB;
struct data
{
int en,next;
} edge[N*2];
int tot,head[N];
int sum[2*N];
int id[N] , low[N] , ind;
void add_edge(int st,int en)
{
edge[tot].en=en;
edge[tot].next=head[st];
head[st]=tot++;
}
void dfs(int v,int pre,int dep)
{
id[v]=++ind;
for(int i=head[v];i!=-1;i=edge[i].next)
{
if(edge[i].en==pre) continue;
dfs(edge[i].en,v,dep+1);
}
low[v]=ind;
}
void update(int *a,int pos,int val)
{
while(pos<2*N)
{
a[pos]+=val;
pos+=pos&(-pos);
}
}
int get(int *a,int pos)
{
int tmp=0;
while(pos>0)
{
tmp+=a[pos];
pos-=pos&(-pos);
}
return tmp;
}
int main()
{
int n,a,b;
memset(head,-1,sizeof(head));
tot=0;
scanf("%d",&n);
for(int i=1;i<n;i++)
{
scanf("%d%d",&a,&b);
add_edge(a,b);
add_edge(b,a);
}
memset(id,0,sizeof(id));
memset(low,0,sizeof(low));
ind=0;
dfs(1,-1,0);
memset(sum,0,sizeof(sum));
for(int i=1;i<=n;i++)
update(sum,id[i],1);
char s[2]; int pos , m;
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
scanf("%s%d",s,&pos);
if(s[0]=='Q') printf("%d\n",get(sum,low[pos])-get(sum,id[pos]-1));
else
{
int val=get(sum,id[pos])-get(sum,id[pos]-1);
if(val==1)
update(sum,id[pos],-1);
else
update(sum,id[pos],1);
}
}
return 0;
}