[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;
}

浙公网安备 33010602011771号