[BZOJ1759] Let there be rainbows!题解

题意

\(n (n \leq 200000)\) 个点构成一棵树,开始时任两点之间的边全是灰色的。现决定将已有的边重新涂色。每次操作将选择两个点 \(A,B\) 和一种颜色 $ C(1 \leq C \leq 7)\(,并\)A,B$之间颜色不是 \(C\) 的道路涂成 \(C\)。求输出每种颜色被使用了多少次.

思路

每次选择两个点,把两个点之间的所有边赋一个边权,所以貌似有一点区间推平的感觉了,所以使用区间推平线段树,然后我们想一想如何转化到线段树上,由于给定的是一颗树,于是可以使用树链剖分。

但是我们发现,树链剖分剖出来的链是以点作为端点的,但我们要处理的是边权,于是考虑边权下放到点权。

时间复杂度

预处理两遍 DFS 的时间复杂度为 \(O(n)\) 的,对于每次操作,树链剖分跳链不超过 \(logn\) 条(感性理解:每走过一条轻边, 子树大约减少一半),线段树查询为 \(O(logn)\) 的,所以树剖的复杂度为 \(O(log^2 n)\) ,整体复杂度 \(O(n + q \times log^2 n)\) 再加上线段树的大常数,看似过不了,但链数通常不到 \(logn\) 条,所以能过,最大的只跑了 600ms。

一些细节

  • 由于边权下放,所以两点的 LCA 所表示的边权是不是属于两点路径上的边,所以不处理两点的 LCA。
  • 如果每次操作所有边,更改为不同权值,会炸 int
  • lazy_tag 赋初值为 -1。

Code:

#include<bits/stdc++.h>
using namespace std;
namespace IO{
	inline long long read() {
		long long res = 0; bool f = 0;
		char ch = getchar();
		while (ch < '0' || ch > '9')
			f |= (ch == '-'), ch = getchar();
		while (ch >= '0' && ch <= '9')
			res = (res << 3) + (res << 1) + ch - '0', ch = getchar();
		return f ? -res : res;
	}
}
using IO::read;
const int MAXN = 200000 + 5;
struct e{
	int to, nxt;
}edge[MAXN << 1];
long long ans[8];
int head[MAXN];
int top[MAXN];
int dfn[MAXN];
int siz[MAXN];
int fa[MAXN];
int dep[MAXN];
int son[MAXN];
int dfncnt;
int cnt;
int n;
int q;
void add_edge(int u, int v) {
	edge[++cnt].to = v, edge[cnt].nxt = head[u], head[u] = cnt;
}
/*区间推平线段树*/
int lazy_tag[MAXN << 2];
int color[MAXN << 2][8];
#define ls (root << 1)
#define rs (root << 1 | 1)
#define mid (l + r >> 1)
#define ltree ls, l, mid
#define rtree rs, mid + 1, r
void cover(int root, int l, int r, int val) {
	for (int i = 1; i <= 7; ++i) {
		color[root][i] = 0;
	}
	color[root][val] = (r - l + 1);
	lazy_tag[root] = val;
}
void push_down(int root, int l, int r) {
	if (~lazy_tag[root]) 
		return cover(ltree, lazy_tag[root]), cover(rtree, lazy_tag[root]), lazy_tag[root] = -1, void();
}
void update(int root, int l, int r, int s, int t, int val) {
	if (s <= l && r <= t) {
		ans[val] += (r - l + 1) - color[root][val];
		return cover(root, l, r, val), void();
	}
	push_down(root, l, r);
	if (s <= mid)
		update(ltree, s, t, val);
	if (t > mid)
		update(rtree, s, t ,val);
	for (int i = 1; i <= 7; ++i)
		color[root][i] = color[ls][i] + color[rs][i];
}
#undef ls
#undef rs
#undef mid
#undef ltree
#undef rtree
/*树链剖分*/ 
void dfs1(int u, int f) {
	dep[u] = dep[fa[u]] + 1;
	siz[u] = 1;
	fa[u] = f;
	for (int i = head[u]; ~i; i = edge[i].nxt) {
		int v = edge[i].to;
		if (v == f)
			continue;
		fa[v] = u;
		dfs1(v, u);
		siz[u] += siz[v]; 
		if (siz[son[u]] < siz[v])
			son[u] = v;
	} 
//	return siz[u];
}
void dfs2(int u, int tp, int f) {
	dfn[u] = ++dfncnt, top[u] = tp;
	if(!son[u])
		return;
	dfs2(son[u], tp, u);
	for (int i = head[u]; ~i; i = edge[i].nxt) {
		int v = edge[i].to;
		if (v == f || v == son[u])
			continue;
		dfs2(v, v, u);
	}
}
void change(int u, int v, int w) {
	while (top[u] != top[v]) {
		if (dep[top[u]] < dep[top[v]]) 
			swap(u, v);
		update(1, 1, dfncnt, dfn[top[u]], dfn[u], w);
		u = fa[top[u]];
	}
	if (dep[u] < dep[v])
		swap(u, v);
	if (u == v)
		return; 
	update(1, 1, dfncnt, dfn[v] + 1, dfn[u], w);
}
int main() {
	freopen("d.in", "r", stdin);
	freopen("d.out", "w", stdout);
	memset(lazy_tag, -1, sizeof(lazy_tag));
	memset(head, -1, sizeof(head));
	n = read();
	for (int i = 1, u, v; i < n; ++i) {
		u = read(), v = read();
		add_edge(u, v), add_edge(v, u);
	}
//	return 0;
	dfs1(1, 0);
//	return 0;
	dfs2(1, 1, 0); 
	cin >> q;
	int u, v, w;
	while (q--) {
		u = read(), v = read(), w = read();
		change(u, v, w);
	}
	for (int i = 1; i <= 7; ++i)
		cout << ans[i] << '\n';
	return 0;
}
posted @ 2025-10-15 22:03  孤独的Bochi  阅读(3)  评论(0)    收藏  举报