动态树之树链剖分

一些不需要link-cut操作的树上路径的题可以用树链剖分做,常数比lct小多了。

学习了下hld(树链剖分),嗯,挺简单的。hld可以在树中的操作有很多,hld可以说只是一种概念结构,它可以套很多其它的数据结构来进行操作,比如我现在只要求路径最值和求和,那么套线段树就行了;如果我要求第k大,可以套splay和主席树(这个不知道),也可以套分块(不会,分块以后学,必须学。。)但是我觉得,树剖比lct还要难写。。我lct一下就能写出来了。。可是lct的常数,不忍直视。。概念:

重儿子:num[u]为v的子节点中num值最大的,那么u就是v的重儿子。
    轻儿子:v的其它子节点。
    重边:点v与其重儿子的连边。
    轻边:点v与其轻儿子的连边。
    重链:由重边连成的路径。
    轻链:轻边。
    剖分后的树有如下性质:
    性质1:如果(v,u)为轻边,则siz[u] * 2 < siz[v];
    性质2:从根到某一点的路径上轻链、重链的个数都不大于logn。

我们来说他怎么在路径操作吧:

下边是一个例图:黑边为重边,带红点的为重边组成的链的最顶点(没有重边的点最顶点就是它自己),蓝字为重边的序号

哈哈,发现了什么吗?

重链的序号是连续的。 不会有两条重链相交~~

哈哈?套各种树维护啦~~~。

我们在这个连续的区间操作就行了,然后不断向根走,直到走到最顶点相交(这里不是重链相交,是最顶点相交,即一条轻边和重边的交点)

向上走的时候,不是一个个走,那么效率大大提高啦~。。这就是树链剖分的主体思想。

我们在这些链(或点)重新标号后,用各种数据结构维护信息。

求树剖需要维护的域很简单,两个深搜搞定,大家自己想吧,我不说了(要是想不通,,点这:http://blog.csdn.net/jiangshibiao/article/details/24669751

例题:

基于点分类:【BZOJ】1036: [ZJOI2008]树的统计Count(lct/树链剖分)

#include <cstdio>
#include <iostream>
using namespace std;
#define dbg(x) cout << #x << "=" << x << endl
#define read(x) x=getint()
#define print(x) printf("%d", x)
#define lc x<<1
#define rc x<<1|1
#define lson l, m, lc
#define rson m+1, r, rc
#define MID (l+r)>>1

const int oo=~0u>>1;
inline int getint() { char c; int ret=0, k=1; for(c=getchar(); c<‘0‘ || c>‘9‘; c=getchar()) if(c==‘-‘) k=-1; for(; c>=‘0‘&&c<=‘9‘; c=getchar()) ret=ret*10+c-‘0‘; return k*ret; }

const int N=30010, M=100005;
int ihead[N], inext[M], to[M], cnt, n, m;
int top[N], son[N], fa[N], dep[N], sz[N], id[N], a[N], b[N], tot;
int L, R, key;

struct node { int mx, sum; }t[N*50];
inline const int max(const int& a, const int& b) { return a>b?a:b; }
inline void pushup(const int &x) { t[x].mx=max(t[lc].mx, t[rc].mx); t[x].sum=t[lc].sum+t[rc].sum; }

void dfs1(const int &u) {
	sz[u]=1; int v;
	for(int i=ihead[u]; i; i=inext[i]) if(fa[u]!=(v=to[i])) {
		fa[v]=u;
		dep[v]=dep[u]+1;
		dfs1(v);
		sz[u]+=sz[v];
		if(sz[v]>sz[son[u]]) son[u]=v;
	}
}

void dfs2(const int &u, const int &tp) {
	id[u]=++tot; top[u]=tp; b[tot]=a[u];
	if(son[u]) dfs2(son[u], tp);
	for(int i=ihead[u]; i; i=inext[i]) if(to[i]!=fa[u] && to[i]!=son[u]) dfs2(to[i], to[i]);
}

void build(const int &l, const int &r, const int &x) {
	if(l==r) { t[x].mx=t[x].sum=b[l]; return; }
	int m=MID;
	build(lson); build(rson);
	pushup(x);
}

void update(const int &l, const int &r, const int &x) {
	if(l==r) { t[x].mx=t[x].sum=key; return; }
	int m=MID;
	if(L<=m) update(lson);
	if(m<R) update(rson);
	pushup(x);
}

int getmax(const int &l, const int &r, const int &x) {
	if(L<=l && r<=R) return t[x].mx;
	int m=MID, mx=oo+1;
	if(L<=m) mx=max(mx, getmax(lson));
	if(m<R) mx=max(mx, getmax(rson));
	return mx;
}

int query(const int &l, const int &r, const int &x) {
	if(L<=l && r<=R) return t[x].sum;
	int m=MID, ret=0;
	if(L<=m) ret+=query(lson);
	if(m<R) ret+=query(rson);
	return ret;
}

inline int getmax(int x, int y) {
	int fx=top[x], fy=top[y], ret=oo+1;
	while(fx!=fy) {
		if(dep[fx]<dep[fy]) { swap(x, y); swap(fx, fy); }
		L=id[fx], R=id[x];
		ret=max(ret, getmax(1, n, 1));
		x=fa[fx]; fx=top[x];
	}
	if(dep[x]>dep[y]) swap(x, y);
	L=id[x], R=id[y];
	return max(ret, getmax(1, n, 1));
}

inline int query(int x, int y) {
	int fx=top[x], fy=top[y], ret=0;
	while(fx!=fy) {
		if(dep[fx]<dep[fy]) { swap(x, y); swap(fx, fy); }
		L=id[fx], R=id[x];
		ret+=query(1, n, 1);
		x=fa[fx]; fx=top[x];
	}
	if(dep[x]>dep[y]) swap(x, y);
	L=id[x], R=id[y];
	return ret+query(1, n, 1);
}

inline void add(const int &u, const int &v) {
	inext[++cnt]=ihead[u]; ihead[u]=cnt; to[cnt]=v;
	inext[++cnt]=ihead[v]; ihead[v]=cnt; to[cnt]=u;
}

int main() {
	read(n);
	int u, v, ans;
	for(int i=1; i<n; ++i) {
		read(u); read(v);
		add(u, v);
	}
	for(int i=1; i<=n; ++i) read(a[i]);
	dfs1(1);
	dfs2(1, 1);
	build(1, n, 1);
	char c[10];
	read(m);
	for(int i=0; i<m; ++i) {
		scanf("%s", c);
		if(c[0]==‘C‘) {
			read(u); read(key); L=R=id[u];
			update(1, n, 1);
		}
		else if(c[0]==‘Q‘) {
			read(u); read(v);
			if(c[1]==‘M‘) ans=getmax(u, v);
			else ans=query(u, v);
			printf("%d\n", ans);
		}
	}
	return 0;
}

动态树之树链剖分

时间: 2024-08-02 10:35:16

动态树之树链剖分的相关文章

(中等) Hiho 1232 Couple Trees(15年北京网络赛F题),主席树+树链剖分。

"Couple Trees" are two trees, a husband tree and a wife tree. They are named because they look like a couple leaning on each other. They share a same root, and their branches are intertwined. In China, many lovers go to the couple trees. Under t

树链剖分+线段树 HDOJ 4897 Little Devil I(小恶魔)

题目链接 题意: 给定一棵树,每条边有黑白两种颜色,初始都是白色,现在有三种操作: 1 u v:u到v路径(最短)上的边都取成相反的颜色 2 u v:u到v路径上相邻的边都取成相反的颜色(相邻即仅有一个节点在路径上) 3 u v:查询u到v路径上有多少个黑色边 思路: 对树进行树链剖分,分成重链和轻链,用两棵线段树W,L来维护.W维护树上在重链上的u和v之间的边的翻转情况(操作在线段树上的[pos[v],pos[u]]区间):L维护树上在重链上的u和v之间的相邻边的翻转情况.那么某一个点u与它父

长链剖分总结

长链剖分总结 概念 长链剖分和轻重链剖分十分相似,都是将一棵树节点的信息分成多条链的信息,但是前者是以深度剖分,后者则是以子树大小来剖分. 同时长链剖分还借鉴了$dsu\;on\;tree$的一些$trick$使得它能十分高效地合并子树信息. 性质 破天荒地写了证明 性质一 所有链长度之和为节点数 证明: 每个点在且仅在一条链中 性质二 任意一个点$k$级祖先所在长链的长度一定大于等于$k$ 证明: 假如$y$所在长链的长度小于$k$,那么它所在的链一定不是重链,因为$x-y$这条链显然更优,那

【bzoj3589】动态树 树链剖分+线段树

题目描述 别忘了这是一棵动态树, 每时每刻都是动态的. 小明要求你在这棵树上维护两种事件 事件0:这棵树长出了一些果子, 即某个子树中的每个节点都会长出K个果子. 事件1:小明希望你求出几条树枝上的果子数. 一条树枝其实就是一个从某个节点到根的路径的一段. 每次小明会选定一些树枝, 让你求出在这些树枝上的节点的果子数的和. 注意, 树枝之间可能会重合, 这时重合的部分的节点的果子只要算一次. 输入 第一行一个整数n(1<=n<=200,000), 即节点数. 接下来n-1行, 每行两个数字u,

Tsinsen A1517. 动态树 树链剖分,线段树,子树操作

题目 : http://www.tsinsen.com/A1517 A1517. 动态树 时间限制:3.0s   内存限制:1.0GB 总提交次数:227   AC次数:67   平均分:49.52 将本题分享到: 查看未格式化的试题   提交   试题讨论 试题来源 中国国家队清华集训 2013-2014 第四天 问题描述 小明在楼下种了一棵动态树, 该树每天会在某些节点上长出一些果子. 这棵树的根节点为1, 它有n个节点, n-1条边. 别忘了这是一棵动态树, 每时每刻都是动态的. 小明要求

【BZOJ-3589】动态树 树链剖分 + 线段树 + 线段覆盖(特殊的技巧)

3589: 动态树 Time Limit: 30 Sec  Memory Limit: 1024 MBSubmit: 405  Solved: 137[Submit][Status][Discuss] Description 别忘了这是一棵动态树, 每时每刻都是动态的. 小明要求你在这棵树上维护两种事件 事件0: 这棵树长出了一些果子, 即某个子树中的每个节点都会长出K个果子. 事件1: 小明希望你求出几条树枝上的果子数. 一条树枝其实就是一个从某个节点到根的路径的一段. 每次小明会选定一些树枝

【bzoj4999】This Problem Is Too Simple! 树链剖分+动态开点线段树

题目描述 给您一颗树,每个节点有个初始值. 现在支持以下两种操作: 1. C i x(0<=x<2^31) 表示将i节点的值改为x. 2. Q i j x(0<=x<2^31) 表示询问i节点到j节点的路径上有多少个值为x的节点. 输入 第一行有两个整数N,Q(1 ≤N≤ 100,000:1 ≤Q≤ 200,000),分别表示节点个数和操作个数. 下面一行N个整数,表示初始时每个节点的初始值. 接下来N-1行,每行两个整数x,y,表示x节点与y节点之间有边直接相连(描述一颗树).

BZOJ 3589 动态树 树链剖分+容斥定理

题目大意:给出一棵树,每一个节点有一个权值,一开始所有节点的权值都是0.有两种操作,0 x y代表以x为根节点的子树上所有点的权值增加y.1 k a1 b1 a2 b2 --ak bk代表询问.一共有k条边( k <= 5),这些边保证是一个点到根节点的路径上的一段.问这些路径段上的点的权值和是多少,可能有多条路径重叠的情况. 思路:子树修改,区间查询,很明显用树链剖分解决,树链剖分维护一个size域,那么x的子树的范围就是pos[x]到pos[x] + size[x] - 1这一段上,可以用线

BZOJ 3589 动态树 树链剖分+容斥原理

题目大意:给定一棵以1为根的有根树,每个节点有点权,提供两种操作: 1.以某个节点为根的子树所有节点权值+x 2.求一些链的并集的点权和,其中这些链都是由某个节点出发指向根 首先子树修改,链上查询,树链剖分的WT~ 然后这些链上的每个点的点权都只能被加一次,肯定不能打标记,由于k<=5,我们考虑容斥原理 总权值=单链-两两之交+三链之交-- 状压枚举即可 两条链的交集求法如下: 1.求两条链底的LCA 2.若LCA的深度小于其中一条链的链顶深度 交集为空 返回0 3.返回一条链 链底为LCA 链