思路:
有两种写法:树链剖分 + 权值线段树、树上线段树合并。
此题是一个线段树合并的板子题,做下笔记:
树上线段树合并,本质就是递归过程中合并两两节点线段树信息,彷佛很暴力但魔幻的能优化为 O ( n l o g n ) O(nlogn) O(nlogn) 的复杂度,类似启发式合并。
具体写法:对树中任意点 动态 地建一颗线段树。因为只需要最终输出,此算法为离线算法,对树上一条路径存放救济粮的过程完全可以用 树上差分 优化。
合并模板:
//离线
int merge(int u, int v, int l, int r) {
if (!u)return v;//任意点不存在就返回兄弟结点,这就是能保证O(nlogn)的关键
if (!v)return u;
if (l == r) {
tr[u].mx += tr[v].mx;
tr[u].pos = tr[u].mx ? l : 0;
//中间这部分依附过程会破坏旧信息,只能离线算法中用,优点是不多占用空间
return u;
}
int mid = l + r >> 1;
tr[u].l = merge(tr[u].l, tr[v].l, l, mid);
tr[u].r = merge(tr[u].r, tr[v].r, mid + 1, r);
//指针修改后再pushup维护信息
pushup(u);
return u;
}
//在线
int merge(int u, int v, int l, int r) {
if (!u)return v;//任意点不存在就返回兄弟结点
if (!v)return u;
int p = ++rdx;
if (l == r) {
tr[p].mx = tr[u].mx + tr[v].mx;
tr[p].pos = tr[p].mx ? l : 0;
return p;
}
int mid = l + r >> 1;
tr[p].l = merge(tr[u].l, tr[v].l, l, mid);
tr[p].r = merge(tr[u].r, tr[v].r, mid + 1, r);
pushup(p);
return p;
}
C o d e : Code: Code:
#include
#include
#include
#define mem(a,b) memset(a,b,sizeof a)
#define cinios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
#define debug(x) cout<<"target is "<<x<<endl
#define forr(a,b,c) for(int a=b;a<=c;a++)
#define all(a) a.begin(),a.end()
#define oper(a) operator<(const a& ee)const
#define endl "\n"
#define ul (u << 1)
#define ur (u << 1 | 1)
//#pragma GCC optimize(3, "Ofast", "inline")
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int N = 1e5 + 10, M = 200010, MM = 110;
int INF = 0x3f3f3f3f, mod = 1e9 + 7;
ll LNF = 0x3f3f3f3f3f3f3f3f;
int n, m, k, siz;
int h[N], ne[M], e[M], idx;
int fa[N][17], dep[N], ans[N];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dfs_pre(int x, int fat) {
for (int i = h[x]; ~i; i = ne[i]) {
int j = e[i];
if (j == fat)continue;
dep[j] = dep[x] + 1;
fa[j][0] = x;
for (int k = 1; k <= 16; k++)
fa[j][k] = fa[fa[j][k - 1]][k - 1];
dfs_pre(j, x);
}
}
int lca(int a, int b) {
if (dep[a] < dep[b])swap(a, b);
for (int i = 16; i >= 0; i--)
if (dep[fa[a][i]] >= dep[b])
a = fa[a][i];
if (a == b)return a;
for (int i = 16; i >= 0; i--)
if (fa[a][i] != fa[b][i]) {
a = fa[a][i];
b = fa[b][i];
}
return fa[a][0];
}
//线段树合并能做到玄学的 O(nlogn)
struct tree
{
int l, r;
int mx, pos;
}tr[N * 60];//空间理论要开 N * 4 * 4 * logn,实际没那么多
int root[N], rdx;
void pushup(tree& root, tree& le, tree& re) {
root.mx = max(le.mx, re.mx);
root.pos = le.mx >= re.mx ? le.pos : re.pos;
}
void pushup(int u) {
pushup(tr[u], tr[tr[u].l], tr[tr[u].r]);
}
void modify(int u, int l, int r, int x, int d) {
if (l == r) {
tr[u].mx += d;
tr[u].pos = tr[u].mx ? l : 0;
return;
}
int mid = l + r >> 1;
if (x <= mid) {
if (!tr[u].l)tr[u].l = ++rdx;
modify(tr[u].l, l, mid, x, d);
}
else {
if (!tr[u].r)tr[u].r = ++rdx;
modify(tr[u].r, mid + 1, r, x, d);
}
pushup(u);
}
//线段树离线合并函数,如果是在线就新建点 p,不要影响原有数据
int merge(int u, int v, int l, int r) {
if (!u)return v;//任意点不存在就返回兄弟结点
if (!v)return u;
if (l == r) {
tr[u].mx += tr[v].mx;
tr[u].pos = tr[u].mx ? l : 0;//差分维护后处理位置信息
return u;
}
int mid = l + r >> 1;
tr[u].l = merge(tr[u].l, tr[v].l, l, mid);
tr[u].r = merge(tr[u].r, tr[v].r, mid + 1, r);
pushup(u);
return u;
}
void dfs(int x, int fat) {
for (int i = h[x]; ~i; i = ne[i]) {
int j = e[i];
if (j == fat)continue;
dfs(j, x);
root[x] = merge(root[x], root[j], 1, siz);
//和子节点的线段树一一合并
}
ans[x] = tr[root[x]].pos;
//合并后记入答案
}
void solve() {
cin >> n >> m;
mem(h, -1);
siz = 1e5;//线段树每个位置维护的是某个物品 z
//区间维护最大个数和位置
for (int i = 2; i <= n; i++) {
int a, b;
cin >> a >> b;
add(a, b), add(b, a);
}
dep[0] = 0, dep[1] = 1;
dfs_pre(1, -1);
for (int i = 1; i <= n; i++)root[i] = ++rdx;
//每个点建树根
for (int i = 1; i <= m; i++) {
int a, b, x;
cin >> a >> b >> x;
int p = lca(a, b);
//差分思想,不过是将差分的过程用线段树合并优化了
modify(root[a], 1, siz, x, 1);
modify(root[b], 1, siz, x, 1);
modify(root[p], 1, siz, x, -1);
if (fa[p][0])modify(root[fa[p][0]], 1, siz, x, -1);
}
dfs(1, -1);
forr(i, 1, n)cout << ans[i] << endl;
}
signed main() {
cinios;
int T = 1;
forr(t, 1, T) {
solve();
}
return 0;
}
/*
*/