树链剖分(bzoj 1036: [ZJOI2008]树的统计Count)

来源:互联网 发布:福大图书馆数据库 编辑:程序博客网 时间:2024/05/23 05:09

树链剖分:

把一棵树剖分为若干条链,然后利用数据结构(树状数组,SBT,Splay,线段树等等)去维护每一条链,复杂度

为O(logn),总体复杂度O(nlog²n)


步骤:

①将树的边分成重边和轻边,令siz[u]为u节点所有子节点的个数,v是u所有儿子中siz[]值最大的,

那么边(u,v)就是重边,否则就是轻边

深搜一次就好,顺便求出每个节点的深度和每个节点的父亲是谁

数组:在第一次深搜时全部搜出

fa[u]      ------      u节点的父亲(根的父亲为0)

siz[u]      ------      u节点所有子节点的个数(叶子节点的siz[]值显然为0)

dep[u]      ------      u节点的深度(根的深度为1)

son[u]      ------      u节点的重儿子 [边(u,son[u])为重边]


②第二次深搜,将树分解成链,[重边->重链,轻边->将两条(个)重链或叶子连接在一起],

并求出每个节点所在链的顶端节点

深搜顺序:如果当前节点是重链上的节点,则随着这条重链往下搜,搜完回溯后再搜索与这个点相连的其他轻儿子

最后每个节点对应线段(链)中的编号就是搜索的dtime

数组:在第二次搜索时全部搜出

top[u]      ------      u点所在链的顶端节点

rak[u]      ------     树中u节点剖分成链后对应的新编号

id[x]      ------      链中编号x的点对应的树的节点(反rak[])

经过前两次操作(两次搜索),树的剖分就完成了,树的问题就可以转化成链的问题


如上图:红色的边是重边,蓝色的边是轻边,红色的数字对应着当前节点在链中的编号,红点表示每个链的链头

性质:轻边(u,v)中,size(v)<=size(u/2)

从根到某一点的路径上,不超过logn条轻边和不超过logn条重边

对于每条重链,从链头到链尾编号从小到大



③:附录,例如这道题,如何修改某个节点u的值呢?

直接线段树单点更新,更新rak[u]点的值即可

如果查询两个节点u和v之间所有节点的和?

如果u和v在同一条重链中,那么好办,直接线段树区间查询一次即可,如果不在一条链中

就要让u点去找v点,不停地执行以下操作,让u点和v在同一条重链上,之后执行上个步骤

①u点爬到它所在重链的顶端节点,求出爬过的这段链的所有节点权值之和

②从该顶端节点爬向它的父亲(跑到另一条重链上了),之和继续执行步骤①

注意,如果每次u都是往上爬,深度是越来越低的!假设低过了v点的深度!那么u和v永远不可能碰面了,

所以每次爬的时候倒要比较下u和v的深度,让深度低的那个爬!!这也是为什么要求每个节点的深度

可能很难讲清楚,直接看代码好懂点

专门拉出来

int TreQuerys(int x, int y)/*步骤③,模拟u和v相遇的过程,不停地线段树查询走过节点的权值和*/{int sum, p1, p2;p1 = top[x], p2 = top[y], sum = 0;while(p1!=p2){if(dep[p1]<dep[p2])/*让深度低的那个往上爬*/swap(p1, p2), swap(x, y);sum += Querys(1, n, 1, rak[p1], rak[x]);/*普通线段树查询,下同*/x = fa[p1], p1 = top[x];/*我要爬了*/}if(dep[x]>dep[y])swap(x, y);sum += Querys(1, n, 1, rak[x], rak[y]);return sum;}


1036: [ZJOI2008]树的统计Count

Time Limit: 10 Sec  Memory Limit: 162 MB
Submit: 16866  Solved: 6868
[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之间。

Output

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

Sample Input

4
1 2
2 3
4 1
4 2 1 3
12
QMAX 3 4
QMAX 3 3
QMAX 3 2
QMAX 2 3
QSUM 3 4
QSUM 2 1
CHANGE 1 5
QMAX 3 4
CHANGE 3 6
QMAX 3 4
QMAX 2 4
QSUM 3 4

Sample Output

4
1
2
2
10
6
5
6
5
16


AC代码

#include<stdio.h>#include<vector>#include<string.h>#include<algorithm>using namespace std;#define inf 2147483647;vector<int> G[30005];typedef struct{int sum;int max;}Tree;Tree tre[125005];int n, val[30005], son[30005], fa[30005], siz[30005], dep[30005];int k, top[30005], rak[30005], id[30005];void Sechs(int u, int p)/*步骤①*/{int v, i;fa[u] = p;dep[u] = dep[p]+1;for(i=0;i<G[u].size();i++){v = G[u][i];if(v==p)continue;Sechs(v, u);siz[u] += siz[v]+1;if(son[u]==0 || siz[v]>siz[son[u]])son[u] = v;}}void Sechr(int u, int p)/*步骤②*/{int v, i;top[u] = p;rak[u] = ++k, id[k] = u;if(son[u]==0)return;Sechr(son[u], p);for(i=0;i<G[u].size();i++){v = G[u][i];if(v==son[u] || v==fa[u])continue;Sechr(v, v);}}void Create(int l, int r, int x);/*四个线段树的简单操作*/void Update(int l, int r, int x, int a, int b);int Querys(int l, int r, int x, int a, int b);int Queryx(int l, int r, int x, int a, int b);int TreQuerys(int x, int y)/*步骤③,模拟u和v相遇的过程,不停地线段树查询走过节点的权值和*/{int sum, p1, p2;p1 = top[x], p2 = top[y], sum = 0;while(p1!=p2){if(dep[p1]<dep[p2])swap(p1, p2), swap(x, y);sum += Querys(1, n, 1, rak[p1], rak[x]);x = fa[p1], p1 = top[x];}if(dep[x]>dep[y])swap(x, y);sum += Querys(1, n, 1, rak[x], rak[y]);return sum;}int TreQueryx(int x, int y){int now, p1, p2;p1 = top[x], p2 = top[y], now = -inf;while(p1!=p2){if(dep[p1]<dep[p2])swap(p1, p2), swap(x, y);now = max(now, Queryx(1, n, 1, rak[p1], rak[x]));x = fa[p1], p1 = top[x];}if(dep[x]>dep[y])swap(x, y);now = max(now, Queryx(1, n, 1, rak[x], rak[y]));return now;}int main(void){int i, x, y, q;char str[15];while(scanf("%d", &n)!=EOF){for(i=1;i<=n;i++)G[i].clear();for(i=1;i<=n-1;i++){scanf("%d%d", &x, &y);G[x].push_back(y);G[y].push_back(x);}for(i=1;i<=n;i++)scanf("%d", &val[i]);memset(siz, 0, sizeof(siz));memset(son, 0, sizeof(son));k = 0;Sechs(1, 0);Sechr(1, 1);for(i=1;i<=n;i++)printf("%d ", rak[i]);scanf("%d", &q);Create(1, n, 1);while(q--){scanf("%s%d%d", str+1, &x, &y);if(str[1]=='C')Update(1, n, 1, rak[x], y);else if(str[2]=='M')printf("%d\n", TreQueryx(x, y));elseprintf("%d\n", TreQuerys(x, y));}}return 0;}void Create(int l, int r, int x){int m;if(l==r){tre[x].sum = tre[x].max = val[id[l]];return;}m = (l+r)/2;Create(l, m, x*2);Create(m+1, r, x*2+1);tre[x].sum = tre[x*2].sum+tre[x*2+1].sum;tre[x].max = max(tre[x*2].max, tre[x*2+1].max);}void Update(int l, int r, int x, int a, int b){int m;if(l==r && r==a){tre[x].max = tre[x].sum = b;return;}m = (l+r)/2;if(a<=m)  Update(l, m, x*2, a, b);else  Update(m+1, r, x*2+1, a, b);tre[x].sum = tre[x*2].sum+tre[x*2+1].sum;tre[x].max = max(tre[x*2].max, tre[x*2+1].max);}int Querys(int l, int r, int x, int a, int b){int m, sum;if(l>=a && r<=b)return tre[x].sum;m = (l+r)/2;sum = 0;if(a<=m)  sum += Querys(l, m, x*2, a, b);if(b>=m+1)  sum += Querys(m+1, r, x*2+1, a, b);return sum;}int Queryx(int l, int r, int x, int a, int b){int m, now;if(l>=a && r<=b)return tre[x].max;m = (l+r)/2;now = -inf;if(a<=m)  now = max(now, Queryx(l, m, x*2, a, b));if(b>=m+1)  now = max(now, Queryx(m+1, r, x*2+1, a, b));return now;}



原创粉丝点击