BSOJ2783:Housewife Wind 树链剖分 单点修改 区间查询

来源:互联网 发布:mysql的配置文件在哪里 编辑:程序博客网 时间:2024/05/16 19:14
2783 -- 【POJ2763】Housewife Wind
Description
  给你N个点的无根树,有Q次询问。0号询问:问佳佳从上一次到达的点到现在给定的点需要走的距离。1号询问:修改树上某条边的权值。对于每个0号询问要求你进行输出。
Input
  输入文件的第一行包含三个整数n, q, s,分别表示这里有n个点,q个询问,佳佳最初位于s点(n<100001, q<100001)。接下来n-1行,每行三个整数a, b和w表示点a与点b直接相连,边上的权值为w(1<=w<=10000),任何两点之间只有一条边直接相连;接下来q行为下面两种类型之一:类型1:0 u表示要求佳佳从目前走到u这个点;类型2:1 i w表示把第i条边的权值改为w。
Output
  对于每个类型为1的问题需要输出一行为一个整数表示佳佳从上一次到达的点走到现在给定的点需要走的距离。
Sample Input

  3 3 1
  1 2 1
  2 3 2
  0 2
  1 2 3
  0 3

Sample Output
  1
  3

线段树给了我们维护一个序列(链)的优秀方法,而对于一棵树,没有办法单纯的用线段树维护,于是我们选择将一棵树拆成多条链,再用线段树维护这么多条链
“对于重链上的路径,我们使用线段树就好。对于轻边,我们暴力处理。”
这样就可以来维护树上的路径。
这个思路被称为树链剖分。
树链剖分在我看来分为2部分。
1.两次DFS,分别为找重边;连重链,建映射。
2.数据结构——线段树,对链进行维护。
细节多得不得了,果然还是动态树更亲切呢,学习树链剖分,权当知识储备咯。

例题:BSOJ 2783,这是一道典型的维护树上路径的问题,思路不多说,代码贴在下。

 
#include<iostream>#include<cstdio>#include<cstring>using namespace std;struct Edge{int to,next;}w[300005];int cnt=0,h[300005]={0};struct SegmentTree{int l,r,sum;}tree[300005];int fa[300005]={0},tid[300005]={0},dep[300005]={0},son[300005]={0},size[300005]={0},top[300005]={0};struct TREE{int x,y,v;}T[300005];int last,tim=0;int n,q;void add(int x,int y){cnt++;w[cnt].to=y;w[cnt].next=h[x];h[x]=cnt;}void DFS1(int x,int f,int d){dep[x]=d;fa[x]=f;size[x]=1;for(int i=h[x];i;i=w[i].next){int to=w[i].to;if(to!=f){DFS1(to,x,d+1);size[x]+=size[to];if(son[x]==0||size[to]>size[son[x]])son[x]=to;}}}void DFS2(int x,int tp){top[x]=tp;tid[x]=++tim;if(son[x]==0)return;//DFS2(son[x],tp);for(int i=h[x];i;i=w[i].next){int to=w[i].to;if(to!=son[x]&&to!=fa[x])DFS2(to,to);}}void Pushup(int x){tree[x].sum=tree[x*2].sum+tree[x*2+1].sum;//}void Build(int l,int r,int Root){tree[Root].l=l;tree[Root].r=r;tree[Root].sum=0;if(l==r)return;//int mid=(l+r)>>1;Build(l,mid,Root*2);Build(mid+1,r,Root*2+1);Pushup(Root);}void Change(int x,int data,int Root){if(tree[Root].l==tree[Root].r&&tree[Root].l==x){tree[Root].sum=data;//return;}int mid=(tree[Root].l+tree[Root].r)>>1;if(x<=mid)Change(x,data,Root*2);else Change(x,data,Root*2+1);Pushup(Root);}int Getsum(int l,int r,int Root){//if(l<=tree[Root].l&&tree[Root].r<=r)return tree[Root].sum;int mid=(tree[Root].l+tree[Root].r)>>1;int sum=0;if(l<=mid)sum+=Getsum(l,r,Root*2);if(r>mid)sum+=Getsum(l,r,Root*2+1);return sum;}int Getans(int x,int y){int t1=top[x],t2=top[y];int ans=0;while(t1!=t2){if(dep[t1]<dep[t2]){swap(x,y);swap(t1,t2);}//   ans+=Getsum(tid[t1],tid[x],1);   x=fa[t1];t1=top[x];}if(x==y)return ans;//if(dep[x]>dep[y])swap(x,y);ans+=Getsum(tid[son[x]],tid[y],1);return ans;}void init(){scanf("%d%d%d",&n,&q,&last);for(int i=1;i<n;i++){scanf("%d%d%d",&T[i].x,&T[i].y,&T[i].v);add(T[i].x,T[i].y);add(T[i].y,T[i].x);}DFS1(1,0,0);DFS2(1,1);Build(1,n,1);for(int i=1;i<n;i++){if(dep[T[i].x]>dep[T[i].y])swap(T[i].x,T[i].y);Change(tid[T[i].y],T[i].v,1);}}int main(){init();int cmd;while(q--){scanf("%d",&cmd);if(cmd==0){   int to;scanf("%d",&to);printf("%d\n",Getans(last,to));last=to;}if(cmd==1){int a,val;scanf("%d%d",&a,&val);Change(tid[T[a].y],val,1);}}return 0;}

坑爹细节如下。
1.DFS1的时候要记得把size[x]设为1.
2.递归tm的加退出条件!如DFS里的son[x]==0和Build里的l==r。
3.标记合并,是合并,所以sum不是加等。
4.Getans时
(1).while里判断swap看top的深浅,交换时top和本身全部交换。
(2). 出while,x和y相等直接退出,否则把x swap成浅的,再Getsum(
tid[son[x]],tid[y],1),一定是son[x]..
5.要映射回去:Getsum()&Change();
0 0
原创粉丝点击