[思路笔记] 线段树合并与你

线段树真的好难调啊呜呜呜。

CF208E Blood Cousins

题目大意

给一棵 \(n\) 个点的树,点编号为 \(1\)\(n\)。共 \(m\) 次询问,每次询问给出一对整数 \(v\)\(p\),求有多少点与点 \(v\) 有共同的 \(p\) 级祖先。

思路

使用倍增求出每个点的祖先数组。离线所有询问,把询问都挂到祖先的点上。

在每个点上以深度为点建立权值线段树,那么 \(v\) 的答案就可以转换为 \(v\)\(k\) 级祖先 \(u\) 的线段树中深度为 \(dep_u+p\) 的点的数量 - 1(\(v\) 点本身需要减掉)。

CF600E Lomsat gelral

题目大意

给一棵 \(n\) 个点的以 \(1\) 为根的有根树,每个点有权值 \(c\)\(c\in[1,n]\)),对于每个 \(i\in[1,n]\),求出以 \(i\) 为根的子树中出现次数最多的权值之和。

思路

以权值为点建立权值线段树,dfs时把子节点的线段树合并到父节点上,查一下次数最多的权值统计下答案。

P4556 [Vani有约会]雨天的尾巴 /【模板】线段树合并

题目大意

给一棵 \(n\) 个点的树,有 \(m\) 次操作,每次操作会在 \(x\)\(y\) 这条链上发放 \(z\) 类型的物品。问 \(m\) 次操作后每个点存放最多的物品是哪种。

思路

用差分把每次操作变成树上差分,权值线段树以物品类型为点建立,答案就是每个点的最大值(记得记数量最多的物品的类型)。

代码

线段树合并真的太抽象了啊啊啊。

const int N = 100010;
int n, m, lg;
int last[N], cnt;
struct edge {
	int to, next;
} e[N << 1];
void addedge(int x, int y) {
	e[++cnt].to = y;
	e[cnt].next = last[x];
	last[x] = cnt;
} 
int dep[N], p[N], f[21][N];
void dfs(int x, int fa) {
	dep[x] = dep[fa] + 1, p[x] = fa;
	for (int i = last[x]; i; i = e[i].next) {
		int v = e[i].to;
		if (v == fa) continue;
		dfs(v, x);
	}
}
int LCA(int x, int y) {
	if (dep[x] > dep[y]) swap(x, y);
	for (int i = lg; i >= 0; i--)
		if (dep[x] <= dep[f[i][y]]) y = f[i][y];
	if (dep[x] < dep[y]) y = f[0][y];
	for (int i = lg; i >= 0; i--)
		if (f[i][x] != f[i][y]) x = f[i][x], y = f[i][y];
	if (x != y) x = y = f[0][x];
	return x;
}
struct SegmentTree {
	int ls[N << 6], rs[N << 6], mx[N << 6], pos[N << 6], cnt;
	void update(int rt) {
		if (mx[ls[rt]] >= mx[rs[rt]]) mx[rt] = mx[ls[rt]], pos[rt] = pos[ls[rt]];
		else mx[rt] = mx[rs[rt]], pos[rt] = pos[rs[rt]];
	}
	void change(int &rt, int L, int R, int x, int p) {
		if (!rt) rt = ++cnt;
		if (L == R) {mx[rt] += p, pos[rt] = L; return ;}
		int mid = L + R >> 1;
		if (x <= mid) change(ls[rt], L, mid, x, p);
		else change(rs[rt], mid + 1, R, x, p);
		update(rt);
	} 
	int merge(int x, int y, int L, int R) {
		if (!x || !y) return x + y;
		if (L == R) {mx[x] += mx[y], pos[x] = L; return x;}
		int mid = L + R >> 1;
		ls[x] = merge(ls[x], ls[y], L, mid);
		rs[x] = merge(rs[x], rs[y], mid + 1, R);
		update(x);
		return x;
	}
} tree;
int rt[N], ans[N];
void dfs2(int x, int fa) {
	for (int i = last[x]; i; i = e[i].next) {
		int v = e[i].to;
		if (v == fa) continue;
		dfs2(v, x);
		rt[x] = tree.merge(rt[x], rt[v], 1, 100000); 
	}
	if (tree.mx[rt[x]]) ans[x] = tree.pos[rt[x]];
}
int main() {
	n = read(), m = read(); lg = log2(n);
	for (int i = 1; i < n; i++) {
		int x = read(), y = read();
		addedge(x, y), addedge(y, x); 
	}
	dep[1] = 1; dfs(1, 1);
	for (int i = 1; i <= n; i++) f[0][i] = p[i];
	for (int i = 1; i <= lg; i++)
		for (int j = 1; j <= n; j++)
			f[i][j] = f[i - 1][f[i - 1][j]];
	for (int i = 1; i <= m; i++) {
		int x = read(), y = read(), z = read();
		int lca = LCA(x, y);
		tree.change(rt[x], 1, 100000, z, 1);
		tree.change(rt[y], 1, 100000, z, 1);
		tree.change(rt[lca], 1, 100000, z, -1);  
		if (p[lca] != lca) tree.change(rt[p[lca]], 1, 100000, z, -1); 
	}
	dfs2(1, 1);
	for (int i = 1; i <= n; i++) printf("%d\n", ans[i]);
	return 0;
}

P3224 [HNOI2012]永无乡

题目大意

\(n\) 个点,编号 \(1\)\(n\),并有互不相同的排名。有两种操作:

  • B x y 表示连接点 \(x\) 和点 \(y\)
  • Q x k 表示询问与 \(x\) 连通的点中第 \(k\) 重要的是哪个点。

思路

用并查集维护连通性,以排名为点建立权值线段树,直接操作就行。

射手座之日

题目大意

给一棵 \(n\) 个点的树和一个长度为 \(n\) 的排列。一个子区间的权值等于子区间中所有元素 LCA 的点权,求所有子区间的权值之和。

思路

考虑树上一个点 \(x\) 作为 LCA 的贡献,这个区间中的点一定都在 \(x\) 的子树中。

以点在排列的位置为点建权值线段树,\(x\) 子树中有这个点就标 \(1\),其余标 \(0\),那么答案区间就是连续一段 \(1\) 的长度搞个组合数。

线段树合并做就好了。

posted @ 2023-02-06 21:33  shiranui  阅读(33)  评论(0编辑  收藏  举报
*/