线段树合并

思路

合并数组的时间复杂度是\(n\times v\),所以肯定在某些时候是无法通过的。这个时候我们可以考虑有些点是根本没有用到过的,所以用权值线段树来更新的好处在于它可以免去一些未出现的节点或者只有一方出现过的节点。

例题:[Vani有约会]雨天的尾巴

思路

首先输入一个树进行树上的两点之间的加减操作,我们可以很容易的想到树上差分,将\(x+1,y+1,lca(x,y)-1,fa[lca(x,y)]-1\),再最后统计答案即可。

在合并时,由于有\(q\leq10^5\)​​次操作,每次改变\(n\leq10^5\)​​次,数组合并时间复杂度为\(O(nq)\)​​会\(~~TLE~~\)​​。因此我们可以考虑用线段树动态开点\(+\)​​合并,时间复杂度为科学的\(O(q\log n)\)​​。

Code

#include<bits/stdc++.h>
using namespace std;

int n, m, q;
int x[100005], y[100005], z[100005], a, b;		//读入 
int e[200005], ne[200005], head[100005], idx;	//邻接表 
int seg[500005], rev[500005], size[500005], fa[500005], depth[500005], son[500005], top[500005], tot;	//树剖求LCA 
int root[100005], cnt;							//root表示每个节点的线段树 
int ans[100005];								//储存答案 

struct SegmentTree{								//线段树 
	int l, r, t, num;							//t表示最大值的点,num表示最大值的值 
}tree[6500005];

template<typename T>
inline void read(T&x){
    x = 0; char q; bool f = 1;
    while(!isdigit(q = getchar()))  if(q == '-')    f = 0;
    while(isdigit(q)){
        x = (x<<1) + (x<<3) + (q^48);
        q = getchar();
    }
    x = f?x:-x;
}

template<typename T>
inline void write(T x){
    if(x < 0){
        putchar('-');
        x = -x;
    }
    if(x > 9)   write(x/10);
    putchar(x%10+'0');
}

inline void add(int u, int v){					//邻接表读入 
	e[++idx] = v, ne[idx] = head[u], head[u] = idx;
}

inline void dfs1(int u, int f){					//树剖LCA 
	fa[u] = f;
	size[u] = 1;
	depth[u] = depth[f] + 1;
	for(register int i = head[u]; i; i = ne[i]){
		int v = e[i];
		if(v == f)	continue;
		dfs1(v, u);
		size[u] += size[v];
		if(size[son[u]] < size[v])	son[u] = v;
	}
}

inline void dfs2(int u){						//树剖LCA 
	if(u == son[fa[u]])	top[u] = top[fa[u]];
	else	top[u] = u;
	seg[u] = ++tot;
	rev[tot] = u;
	if(son[u])	dfs2(son[u]);
	for(register int i = head[u]; i; i = ne[i]){
		int v = e[i];
		if(v == son[u] || v == fa[u])	continue;
		dfs2(v);
	}
}

inline int LCA(int x, int y){					//树剖LCA 
	if(depth[x] < depth[y])	swap(x, y);
	while(top[x] != top[y]){
		if(depth[top[x]] < depth[top[y]])	swap(x, y);
		x = fa[top[x]];
	}
	if(depth[x] < depth[y])	swap(x, y);
	return y;
}

inline void pushup(int u){						//比较左右子树的最大值较大值,赋值到父节点 
	if(tree[tree[u].l].num >= tree[tree[u].r].num)	tree[u].num = tree[tree[u].l].num, tree[u].t = tree[tree[u].l].t;
	else	tree[u].num = tree[tree[u].r].num, tree[u].t = tree[tree[u].r].t;
}

inline int modify(int u, int l, int r, int pos, int val){
	if(!u)	u = ++cnt;							//如果没有当前节点,新建 
	if(l == r){
		tree[u].num += val, tree[u].t = l;		//l==r时更新 
		return u;
	}
	int mid = (l+r) >> 1;
	if(pos <= mid)	tree[u].l = modify(tree[u].l, l, mid, pos, val);
	else	tree[u].r = modify(tree[u].r, mid+1, r, pos, val);
	pushup(u);									//更新父节点 
	return u;
}

inline int merge(int u, int v, int l, int r){
	if(!u)	return v;							//若双方之一没有则直接接到另一端 
	if(!v)	return u;
	if(l == r){
		tree[u].num += tree[v].num;				//l==r时更新,两棵树直接权值相加 
		tree[u].t = l;
		return u;
	}
	int mid = (l+r) >> 1;
	tree[u].l = merge(tree[u].l, tree[v].l, l, mid), tree[u].r = merge(tree[u].r, tree[v].r, mid+1, r);		//查询左右子树 
	pushup(u);									//更新父节点 
	return u;
}

inline void query(int u){
	for(register int i = head[u]; i; i = ne[i])
		if(depth[e[i]] > depth[u]){
			query(e[i]);
			root[u] = merge(root[u], root[e[i]], 1, m);
		}
	if(tree[root[u]].num)	ans[u] = tree[root[u]].t;	//若有值则记录在ans中 
}

int main(){
	read(n), read(q);
	for(register int i = 1; i < n; ++i){
		read(a), read(b);
		add(a, b);
		add(b, a);
	}
	dfs1(1, 0);								//树剖LCA 
	dfs2(1);
	for(register int i = 1; i <= q; ++i){
		read(x[i]), read(y[i]), read(z[i]);
		m = max(m, z[i]);					//记录下线段树的边界 
	}
	for(register int i = 1; i <= q; ++i){
		int lca = LCA(x[i], y[i]);
		root[x[i]] = modify(root[x[i]], 1, m, z[i], 1), root[y[i]] = modify(root[y[i]], 1, m, z[i], 1);		//树上差分操作 
		root[lca] = modify(root[lca], 1, m, z[i], -1);
		if(fa[lca])	root[fa[lca]] = modify(root[fa[lca]], 1, m, z[i], -1);
	}
	query(1);								//离线统计 
	for(register int i = 1; i <= n; ++i){
		write(ans[i]);
		putchar('\n');
	}
	return 0;
}
posted @ 2024-10-09 18:23  Zzzzzzzm  阅读(15)  评论(0)    收藏  举报