P4556 [Vani有约会]雨天的尾巴 (线段树合并 + 树上差分)
Posted TURNINING
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P4556 [Vani有约会]雨天的尾巴 (线段树合并 + 树上差分)相关的知识,希望对你有一定的参考价值。
题意:给一颗树,n个节点,m次操作。每次操作使u到v的路径上每个节点中颜色z的数量加1,最后询问每个节点中数量最多的颜色。
思路:显然,我们得维护每个节点中颜色的最大值。那么我们给每个节点开一颗权值线段树,维护颜数量的最大值。现在关键是如何优化路径操作 —— 树上差分。
关于树上差分的可以参考蓝书上的解释
“根据差分序列的前缀和是原序列"这一原理,在树上可以进行类似的化简,其中"区间操作"对应为"路径操作”,“前缀和"对应为"子树和”。
我们设u,v的lca为t,类似于数列上差分我们让u的z+1, v的z+1, t的z-1,t的父亲-1(存在的话)。现在前缀和的过程就相当于是合并线段树的过程。(这空间消耗也太大了…)
#include<bits/stdc++.h>
using namespace std;
#define endl "\\n"
#define lsn (u << 1)
#define rsn (u << 1 | 1)
#define mid (l + r >> 1)
const int MAXN = 1e5 + 10;
const int MAX_LEN = 1e7;
vector<int> g[MAXN];
int n, m, tot;
int tr[MAX_LEN], cnt[MAX_LEN], ans[MAXN];
int rt[MAX_LEN], ls[MAX_LEN], rs[MAX_LEN];
int dp[21][MAXN], depth[MAXN];
void pushup(int u) {
if(cnt[ls[u]] >= cnt[rs[u]]) {cnt[u] = cnt[ls[u]]; tr[u] = tr[ls[u]];}
else {cnt[u] = cnt[rs[u]]; tr[u] = tr[rs[u]];}
}
void upd(int &u, int l, int r, int p, int d) {
if(!u) u = ++tot;
if(l == r && l == p) {
cnt[u] += d;
tr[u] = p;
}
else {
if(p <= mid) upd(ls[u], l, mid, p, d);
else upd(rs[u], mid+1, r, p, d);
pushup(u);
}
}
int merge(int p, int q, int l, int r) {
if(!p) return q;
if(!q) return p;
if(l == r) {
cnt[p] += cnt[q];
tr[p] = l;
return p;
}
ls[p] = merge(ls[p], ls[q], l, mid);
rs[p] = merge(rs[p], rs[q], mid+1, r);
pushup(p);
return p;
}
void dfs1(int u, int p) {
depth[u] = depth[p] + 1;
dp[0][u] = p;
for(auto v : g[u]) {
if(v == p) continue;
dfs1(v, u);
}
}
void dfs2(int u, int p) {
for(auto v : g[u]) {
if(v == p) continue;
dfs2(v, u);
rt[u] = merge(rt[u], rt[v], 1, 1e5);
}
if(cnt[rt[u]]) ans[u] = tr[rt[u]];
}
int lca(int u, int v) {
if(depth[u] < depth[v]) swap(u, v);
for(int i = 0; i <= 20; i++) {
if((depth[u]-depth[v]) >> i & 1) {
u = dp[i][u];
}
}
if(u == v) return v;
for(int i = 20; i >= 0; i--) {
if(dp[i][u] != dp[i][v]) {
u = dp[i][u];
v = dp[i][v];
}
}
return dp[0][u];
}
void solve() {
cin >> n >> m;
for(int i = 1; i < n; i++) {
int u, v; cin >> u >> v;
g[u].emplace_back(v);
g[v].emplace_back(u);
}
dfs1(1, 0);
for(int i = 0; i + 1 <= 20; i++) {
for(int u = 1; u <= n; u++) {
dp[i+1][u] = dp[i][dp[i][u]];
}
}
while(m--) {
int u, v, x;
cin >> u >> v >> x;
int t = lca(u, v);
upd(rt[u], 1, 1e5, x, 1);
upd(rt[v], 1, 1e5, x, 1);
upd(rt[t], 1, 1e5, x, -1);
if(dp[0][t] != 0) upd(rt[dp[0][t]], 1, 1e5, x, -1);
}
dfs2(1, 0);
for(int i = 1; i <= n; i++) {
cout << ans[i] << endl;
}
}
int main() {
ios::sync_with_stdio(false);
solve();
return 0;
}
以上是关于P4556 [Vani有约会]雨天的尾巴 (线段树合并 + 树上差分)的主要内容,如果未能解决你的问题,请参考以下文章
权值线段树-动态开点-合并(P4556 [Vani有约会]雨天的尾巴
P4556 [Vani有约会]雨天的尾巴 (线段树合并 + 树上差分)
P4556 [Vani有约会]雨天的尾巴 (线段树合并 + 树上差分)
P4556 [Vani有约会]雨天的尾巴 (线段树合并 + 树上差分)