You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

150 lines
5.6 KiB

2 years ago
#include <bits/stdc++.h>
using namespace std;
const int N = 100010, M = N << 1, R = 1e5;
#define ls tr[u].l
#define rs tr[u].r
struct Node {
int l, r;
int max, id; // max 表示物品最大值
} tr[N * 4 * 17]; //为啥开这么大的空间??//TODO
int root[N], cnt; //记录每个节点对应的线段树的根节点的下标
int n, m;
int h[N], e[M], ne[M], idx; //邻接表
int res[N]; //记录每个节点的答案
void add(int a, int b) { //添加边
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
#pragma region 求LCA的倍增模板
int depth[N], f[N][17]; // dep[i] 表示节点 i 的深度fa[i][j] 表示节点 i 向上走 2^j 步到达的节点
void bfs(int t) { //利用递推公式:f[i,j]=f[f[i,j-1],j-1]在bfs遍历过程中填充递推结果数组的值
queue<int> q;
q.push(t);
depth[t] = 1; //根结点的深度是1
while (q.size()) {
int u = q.front();
q.pop();
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (depth[j]) continue; //如果深度为0表示没有访问过防止走回头路
depth[j] = depth[u] + 1; //更新深度
f[j][0] = u; //记录 j的 2^0 步可以跳到的节点是u
for (int k = 1; k <= 16; k++) //利用递推式计算出距离j 2^1 ,2^2,2^3,....的是哪个节点
f[j][k] = f[f[j][k - 1]][k - 1];
q.push(j); //利用j继续扩展
}
}
}
//求a和b的最近公共祖先
int lca(int a, int b) {
if (depth[a] < depth[b]) swap(a, b); //保证a在下b在上
for (int k = 16; k >= 0; k--) //向上跳到b节点的同一层
if (depth[f[a][k]] >= depth[b])
a = f[a][k];
if (a == b) return a; //都是一个节点了,返回谁都是一样的
//开始一起向上跳
for (int k = 16; k >= 0; k--)
if (f[a][k] != f[b][k])
a = f[a][k], b = f[b][k];
//父层就是LCA
return f[a][0];
}
#pragma endregion
void pushup(int u) { //用子节点信息更新父节点信息
tr[u].max = max(tr[ls].max, tr[rs].max);
//如果多个物品数量相同,优先记录最靠左的物品编号
tr[u].id = tr[ls].max >= tr[rs].max ? tr[ls].id : tr[rs].id;
}
void modify(int u, int l, int r, int x, int z) { //将 x 类型 + z
if (l == r) { //找到指定类型
tr[u].max += z; //修改物品最大个数+z
tr[u].id = l; //保存下标
return;
}
int mid = (l + r) >> 1;
if (x <= mid) { //递归左区间
if (!ls) ls = ++cnt; //如果左子节点不存在,动态开点
modify(ls, l, mid, x, z);
} else { //递归右区间
if (!rs) rs = ++cnt; //如果右子节点不存在,动态开点
modify(rs, mid + 1, r, x, z);
}
pushup(u); //用子节点信息更新父节点信息
}
//线段树合并
int merge(int u, int v, int l, int r) {
if (!u) return v; //如果 u 不存在,直接返回 v
if (!v) return u; //如果 v 不存在,直接返回 u
if (l == r) { //如果是单独一个节点
tr[u].max += tr[v].max; //节点合并
//不这样写洛谷第2个测试点WA
//如果某座房屋没有救济粮,则输出 00。
tr[u].id = tr[u].max ? l : 0;
return u;
}
int mid = (l + r) >> 1;
ls = merge(ls, tr[v].l, l, mid); //合并左子节点
rs = merge(rs, tr[v].r, mid + 1, r); //合并右子节点
pushup(u); //用子节点信息更新父节点信息
return u;
}
// dfs求以每个节点为根节点的子树和
void dfs(int u) {
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (depth[j] <= depth[u]) continue; //只能往下走,不要走回头路
dfs(j); //搜索子节点
root[u] = merge(root[u], root[j], 1, R); //合并
}
res[u] = tr[root[u]].id; //记录当前节点的答案(子树和)
}
int main() {
// OJ的环境不使用文件输入输出
#ifndef ONLINE_JUDGE
freopen("P4556.out", "w", stdout);
freopen("P4556.in", "r", stdin);
#endif
//优化读入
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
memset(h, -1, sizeof h); //初始化邻接表
cin >> n >> m;
for (int i = 1; i < n; i++) {
int a, b;
cin >> a >> b;
add(a, b), add(b, a); //无向边
}
bfs(1); //预处理 depth[], f[][],下面要计算LCA这个是基础数据
//为每个节点开一个动态开点的线段树,相当于构建了一个空的,动态的线段树
for (int i = 1; i <= n; i++) root[i] = ++cnt;
for (int i = 0; i < m; i++) { //这里也没有使用离散化所以不用保存Query的结构体代码会更短
int x, y, z;
cin >> x >> y >> z;
int p = lca(x, y); //求出x,y的最近公共祖先
modify(root[x], 1, R, z, 1); // c[x][z] + 1
modify(root[y], 1, R, z, 1); // c[y][z] + 1
modify(root[p], 1, R, z, -1); // c[lca(x, y)][z] - 1
modify(root[f[p][0]], 1, R, z, -1); // c[fa(lca(x, y))][z] - 1
}
//求子树和+线段树合并
dfs(1);
//输出
for (int i = 1; i <= n; i++) printf("%d\n", res[i]);
return 0;
}