BZOJ 1036 树的统计-树链剖分

[ZJOI2008]树的统计Count

Time Limit: 10 Sec Memory Limit: 162 MB
Submit: 12904 Solved: 5191
[Submit][Status][Discuss]
Description

  一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w。我们将以下面的形式来要求你对
这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从点u到点v
的路径上的节点的最大权值 III. QSUM u v: 询问从点u到点v的路径上的节点的权值和
注意:从点u到点v的路径上的节点包括u和v本身

Input

  输入的第一行为一个整数n,表示节点的个数。接下来n – 1行,每行2个整数a和b,表示节
点a和节点b之间有一条边相连。接下来n行,每行一个整数,第i行的整数wi表示节点i的权值。接下
来1行,为一个整数q,表示操作的总数。接下来q行,每行一个操作,以“CHANGE u t”或者
“QMAX u v”或者“QSUM u v”的形式给出。 对于100%的数据,保证1<=n<=30000,0<=q<=200000;
中途操作中保证每个节点的权值w在-30000到30000之间。中途操作中保证每个节点的权值w在-30000
到30000之间。

Output

  对于每个“QMAX”或者“QSUM”的操作,每行输出一个整数表示要求输出的结果。

(代码摘自网络)

  1 /*
  2 关于各个数组和树链剖分的概念
  3 记num[v]表示以v为根的子树的节点数,
  4 dep[v]表示v的深度(根深度为1),
  5 top[v]表示v所在的链的顶端节点,
  6 fa[v]表示v的父亲,
  7 son[v]表示与v在同一重链上的v的儿子节点(姑且称为重儿子),
  8 tree[v]表示v与其父亲节点的连边(姑且称为v的父边)在线段树中的位置。
  9 pre[v]是tree[v]的逆运算
 10 数组名看得懂就OK
 11 只要把这些东西求出来,就能用logn的时间完成原问题中的操作。
 12 重儿子:siz[u]为v的子节点中siz值最大的,那么u就是v的重儿子。
 13 轻儿子:v的其它子节点。
 14 重边:点v与其重儿子的连边。
 15 轻边:点v与其轻儿子的连边。
 16 重链:由重边连成的路径。
 17 轻链:轻边。
 18
 19 剖分后的树有如下性质:
 20 性质1:如果(v,u)为轻边,则siz[u] * 2 < siz[v];
 21 性质2:从根到某一点的路径上轻链、重链的个数都不大于logn。
 22 */
 23 //该题目树中的点有权值而边没有
 24 #include<iostream>
 25 #include<cstdio>
 26 using namespace std;
 27 struct arr{int l,r,sum,max;}a[120005];//线段树
 28 struct adj{int next,go;}adj[60005];//边表
 29 int tree[30005],pre[30005],end[30005]/*end[k]表示以k为出发点的最后一条边的ID*/,son[30005];
 30 int f[30005],data[30005],num[30005],top[30005],deep[30005];
 31 int n,i,x,y,cnt,tot,Q;
 32 char opt[10];
 33 int Max(int a,int b){return (a>b)?a:b;}
 34 void add(int u,int v){adj[++cnt].go=v;adj[cnt].next=end[u];end[u]=cnt;}
 35 void dfs1(int k,int fa,int d)//k是将要填充的节点,fa自然就是他的父亲,d就是深度
 36 {
 37   //该函数计算了节点的重儿子,深度,以该节点为根的子树的节点数,节点的父亲
 38   deep[k]=d;/*记录深度*/f[k]=fa;/*记录父亲*/num[k]=1;/*以k为根的子树的节点数*/
 39   for (int i=end[k]/*初始i为以k为起点的最后一条边的编号*/;i/*当i非0*/;i=adj[i].next/*访问i这条边,并将i赋值为i的下一条边*/)
 40   {
 41     int go=adj[i].go/*边i所指向的节点*/;if (go==fa)/*如果这条边指向他的父亲,则跳过*/ continue;
 42     dfs1(go,k,d+1)/*递归,计算节点go,父亲是k,深度+1*/;num[k]+=num[go];/*累加以k为根的子树的节点数*/
 43     if (!son[k]/*如果说son[k]还为0,说明还未计算*/||num[go]>num[son[k]]/*或者此次计算的子树节点超过了以前的重儿子的节点数,更新他*/) son[k]=go;
 44     //更新该节点的重儿子
 45   }
 46 }
 47 void dfs2(int k,int Number)//Number表示k所在链的顶端节点
 48 {
 49   //该函数计算了k所在链的顶端节点,节点k在线段树中的位置,线段树中某一编号对应的节点
 50   top[k]=Number;tree[k]=++tot; //tree[i] 节点i在线段树中的编号
 51   pre[tree[k]]=k;              //pre[i]  线段树中点为i的对应的节点编号
 52   if (!son[k]) return;    //没有重儿子,说明是叶子节点,退出
 53   dfs2(son[k],Number);        //先递归重儿子,把他的孩子中的“重”部分也置为Number
 54   for (int i=end[k];i;i=adj[i].next)//遍历所有的边
 55   {
 56     int go=adj[i].go;//边所连接到的顶点
 57     if (go!=son[k]/*不是重儿子*/&&go!=f[k]/*不是父亲*/) dfs2(go,go/*一条新的链,顶端节点就是本身*/);  //递归轻儿子
 58   }
 59 }
 60 void build(int k,int l,int r)//构建线段树
 61 {
 62   a[k].l=l;a[k].r=r;
 63   if (l==r) {a[k].sum=a[k].max=data[pre[l]];return;}//不能再分
 64   int mid=(l+r)>>1;
 65   build(k<<1,l,mid);build((k<<1)+1,mid+1,r);//建立完左右子树之后完成自身的建立
 66   a[k].sum=a[k<<1].sum+a[(k<<1)+1].sum;
 67   a[k].max=Max(a[k<<1].max,a[(k<<1)+1].max);
 68 }
 69 void update(int k,int x,int jia)//jia是指更新时所加的变量
 70 {
 71   if (a[k].l==a[k].r)
 72   {
 73     a[k].sum+=jia*(a[k].r-a[k].l+1);
 74     a[k].max+=jia;return;
 75   }
 76   int mid=(a[k].l+a[k].r)>>1;
 77   if (x<=mid) update(k<<1,x,jia);else update((k<<1)+1,x,jia);//更新完左右子树后更新自己
 78   a[k].sum=a[k<<1].sum+a[(k<<1)+1].sum;
 79   a[k].max=Max(a[k<<1].max,a[(k<<1)+1].max);
 80 }
 81 int ask_sum(int k,int x,int y)//此函数和下一个函数是单纯的线段树操作,要给x和y两个端点
 82 {
 83   if (a[k].l>=x&&a[k].r<=y) return a[k].sum;
 84   int mid=(a[k].l+a[k].r)>>1,o=0;
 85   if (x<=mid) o+=ask_sum(k<<1,x,y);//包含左半部分
 86   if (y>mid) o+=ask_sum((k<<1)+1,x,y);//包含右半部分
 87   return o;
 88 }
 89 int ask_max(int k,int x,int y)//这也是线段树,稍作改动即可
 90 {
 91   if (a[k].l>=x&&a[k].r<=y) return a[k].max;
 92   int mid=(a[k].l+a[k].r)>>1,o=-1000000000;
 93   if (x<=mid) o=ask_max(k<<1,x,y);
 94   if (y>mid) o=Max(o,ask_max((k<<1)+1,x,y));
 95   return o;
 96 }
 97 int find_max(int x,int y)
 98 {
 99   int f1=top[x],f2=top[y]/*找到链顶端的节点*/,t,ans=-1000000000;//注意ans要置为-INF
100   while (f1!=f2)//不是一个节点
101   {
102     if(deep[f1]<deep[f2]) t=f1,f1=f2,f2=t,t=x,x=y,y=t;
103     ans=Max(ans,ask_max(1,tree[f1],tree[x]));//上升其中较深的节点,更新答案
104     x=f[f1];f1=top[x];//更新上升过一次的某个节点
105   }
106   ans=Max(ans,(deep[x]>deep[y])?ask_max(1,tree[y],tree[x]):ask_max(1,tree[x],tree[y]));
107   return ans;
108 }
109 int find_sum(int x,int y)//同上
110 {
111   int f1=top[x],f2=top[y],t,ans=0;
112   while (f1!=f2)
113   {
114     if (deep[f1]<deep[f2]) t=f1,f1=f2,f2=t,t=x,x=y,y=t;
115     ans+=ask_sum(1,tree[f1],tree[x]);
116     x=f[f1];f1=top[x];
117   }
118   ans+=(deep[x]>deep[y])?ask_sum(1,tree[y],tree[x]):ask_sum(1,tree[x],tree[y]);
119   return ans;
120 }
121 int main()
122 {
123   scanf("%d",&n);
124   for (i=1;i<n;i++)
125     scanf("%d%d",&x,&y),add(x,y),add(y,x);//添加边
126   for (i=1;i<=n;i++)
127     scanf("%d",&data[i]);//输入点的权值
128   dfs1(1,0,1);dfs2(1,1);//预处理数组
129   build(1,1,n);//建立线段树
130   scanf("%d",&Q);//询问操作
131   while (Q)
132   {
133     Q--;
134     scanf("%s%d%d",opt,&x,&y);
135     if (opt[0]==‘C‘) update(1,tree[x],y-data[x]),data[x]=y;//change操作
136     else if (opt[1]==‘M‘) printf("%d\n",find_max(x,y));//求最大值
137     else printf("%d\n",find_sum(x,y));//求和
138   }
139   return 0;
140 }
时间: 2024-07-31 18:45:19

BZOJ 1036 树的统计-树链剖分的相关文章

kyeremal-bzoj1036[ZJOI2008]-树的统计count-树链剖分

bzoj1036[ZJOI2008]-树的统计count 1036: [ZJOI2008]树的统计Count Time Limit: 10 Sec  Memory Limit: 162 MB Submit: 7567  Solved: 3109 [Submit][Status][Discuss] Description 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w.我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QM

树的统计Count---树链剖分

NEFU专项训练十和十一——树链剖分 Description 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w.我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值 III. QSUM u v: 询问从点u到点v的路径上的节点的权值和 注意:从点u到点v的路径上的节点包括u和v本身 Input 输入的第一行为一个整数n,表示节点的个数.接下来n – 1行,每行2个整

zjoi 2008 树的统计——树链剖分

比较基础的一道树链剖分的题 大概还是得说说思路 树链剖分是将树剖成很多条链,比较常见的剖法是按儿子的size来剖分,剖分完后对于这课树的询问用线段树维护--比如求路径和的话--随着他们各自的链向上走,直至他们在同一条链上为止.比较像lca的方法,只不过这里是按链为单位,而且隔壁的SymenYang说可以用树链剖分做lca..吓哭 然后说说惨痛的调题经历:边表一定要开够啊! 不是n-1 而是2*(n-1)啊! 然后写变量时原始值和映射值要搞清楚啊! 不要搞错了! 还有就是下次求最小值一定看清下界是

【BZOJ 1036】【ZJOI 2008】树的统计 树链剖分模板题

sth神犇的模板: //bzoj1036 题目:一个n个点的树每个点有一个权值,支持修改单点权值,求某两点路径上的点权和或最大点权. #include <cstdio> using namespace std; int pos[30001],f[30001],up[30001],son[30001],size[30001],a[80001],next[80001],last[30001],sum[100001],max[100001];//pos是指某点在线段树中的位置:f是父节点:up是所在

luoguP2590 [ZJOI2008]树的统计 [树链剖分] [TLE的LCT]

题目描述 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w. 我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值 III. QSUM u v: 询问从点u到点v的路径上的节点的权值和 注意:从点u到点v的路径上的节点包括u和v本身 输入输出格式 输入格式: 输入文件的第一行为一个整数n,表示节点的个数. 接下来n – 1行,每行2个整数a和b,表示节点a和节点b之

[JSOI2008][BZOJ1036] 树的统计 - 树链剖分

Description 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w. 我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值 III. QSUM u v: 询问从点u到点v的路径上的节点的权值和 注意:从点u到点v的路径上的节点包括u和v本身 Input & Output Input 输入文件的第一行为一个整数n,表示节点的个数. 接下来n – 1行,每行2个整

[ZJOI2008]树的统计 (树链剖分)

题目描述 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w. 我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值 III. QSUM u v: 询问从点u到点v的路径上的节点的权值和 注意:从点u到点v的路径上的节点包括u和v本身 输入输出格式 输入格式: 输入文件的第一行为一个整数n,表示节点的个数. 接下来n – 1行,每行2个整数a和b,表示节点a和节点b之

【BZOJ 1036】树的统计Count(树链剖分)

[BZOJ 1036]树的统计Count(树链剖分) 1036: [ZJOI2008]树的统计Count Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 12991  Solved: 5233 Description 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w.我们将以下面的形式来要求你对这棵树完成 一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从点u到点v的路径上的节点的最大权

BZOJ 1036: [ZJOI2008]树的统计Count (树链剖分模板题)

1036: [ZJOI2008]树的统计Count Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 14982  Solved: 6081[Submit][Status][Discuss] Description 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w.我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值 I