雨天的尾巴(线段树合并 & 树上差分综合)
点击查看代码
#include <bits/stdc++.h>
#define Re register int
#define LL long long
#define ki cout << endl;
using namespace std;
//打个雨天的尾尾尾尾尾尾尾尾尾尾尾尾尾尾尾尾尾尾尾尾巴
//我复习了lca...............学习了差分....
//可真行
/*
1
2 3
4 5 6 7
8 9
点差分
volume_up
vol[u]++;
vol[v]++;
vol[lca(u, v)] --;
vol[pre[lca(u, v)]] --;
最后求前缀和就是给u - > v路径是都加了个一
eg: 1 2 3 4 5 6(普通差分)
给 2 - > 5 加一
就是 vol[2] + 1 | vol[5] + 1 | vol[6] - 1
求前缀和就是2 -> 5都加一,而五之后没加
给8 -> 5树上每个点都加一(树上差分)
按上边(最上边)操作即可,因为到lca加了两个,而从lca的爹到根都也都多加了2但vol[lca(u, v)]--已经减了一次,所以再减一次就行,而lca留一次,所以只减一个
边差分
vol[u]++;
vol[v]++;
vol[lca(u, v)] -= 2;
边为点到其父亲的边
显而易见lca到跌的边不需要,所以减两个
*/
namespace kiritokazuto {
template <typename T> inline void in(T &x) {
int f = 0; x = 0; char c = getchar();
while(c < '0' || c > '9')f |= c == '-', c = getchar();
while(c >= '0' && c <= '9')x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
x = f ? -x : x;
}
template <typename T> inline void ot(T x) {
if(x < 0)putchar('-'), x = -x;
if(x > 9)ot(x / 10); putchar(x % 10 | '0');
}
}
/*
所以,综上所述,这个题就成了线段树合并的真真正正的板子题
但是合并时可能
出现vol[rt[x]]不为0
但是sum[rt[x]]为0的情况
即节点编号为vol[rt[x]]的救济粮数量为0(就是他这里可能是别的改了)
所以ans[x] = 0
*/
using namespace kiritokazuto;
const int maxn = 6e6 + 10, Inf = 1e5;
int len = 0;
int head[maxn];
int tot;
int n, m;
int ans[maxn];
struct QWMXQ {
int next;
int to;
//int dis;
}a[maxn];
int sum[maxn];
int vol[maxn];
int root[maxn];
struct WMX {
int lson, rson;
#define lsp(x) tr[x].lson
#define rsp(x) tr[x].rson
#define mid ((l + r) >> 1)
}tr[maxn];
//差分直接给每一棵线段树上搞了,所以这里的vol[x]实际上是表示以x为根的子树的答案->从下向上合并的时候
//不是吧....我倍增20位就错,18位就对。。。。????
inline void Qian(int from, int to) {
a[++len].to = to;
//a[len].dis = dis;
a[len].next = head[from];
head[from] = len;
}
int f[maxn][20];//老年倍增数组
int deep[maxn];
inline void pushup(int rt) {
if(lsp(rt) == 0) {
sum[rt] = sum[rsp(rt)];
vol[rt] = vol[rsp(rt)];
return;
}
if(rsp(rt) == 0) {
sum[rt] = sum[lsp(rt)];
vol[rt] = vol[lsp(rt)];
return;
}
if(sum[lsp(rt)] >= sum[rsp(rt)]) {
sum[rt] = sum[lsp(rt)];
vol[rt] = vol[lsp(rt)];
//return;
}else {
sum[rt] = sum[rsp(rt)];
vol[rt] = vol[rsp(rt)];
// return;
}
}
inline void insert(int &rt, int l, int r, int pos, int val) {
if(!rt)rt = ++tot;
if(l == r) {
vol[rt] = pos;//存下该节点编号对应的真正树上的编号->动态开点
sum[rt] += val;
return;//结束啊!!!
}
if(pos <= mid) insert(lsp(rt), l ,mid, pos, val);
else insert(rsp(rt), mid + 1, r, pos, val);
pushup(rt);
}
inline int merge(int rr, int tt, int l, int r) {
if(!rr)return tt;
if(!tt)return rr;
if(l == r) {
sum[rr] += sum[tt];
return rr;//记得返回->叶子
}
lsp(rr) = merge(lsp(rr), lsp(tt), l, mid);
rsp(rr) = merge(rsp(rr), rsp(tt), mid + 1, r);
pushup(rr);//记得pushup
return rr;
}
void dfs(int x, int fa) {
deep[x] = deep[fa] + 1;
f[x][0] = fa;//x 的 2 ^ 0 即 x 向上 翻 1的祖先就是他的爹
for(Re i = 1; i <= 18; i ++) {
f[x][i] = f[f[x][i - 1]][i - 1];//x 的 2 ^ i 的爹是x 的 2 ^ j - 1的 2 ^ j - 1;
}
for(Re i = head[x], to; i ; i = a[i].next) {
to = a[i].to;
if(to == fa)continue;
dfs(to, x);
}
}
int LCA(int a, int b) {
if(deep[a] > deep[b])swap(a, b);//a深,把a换上来
for(Re i = 18; i >= 0; i --) {
if(deep[a] <= deep[f[b][i]])b = f[b][i];//拉到同一层
}
if(a == b)return a;
for(Re i = 18; i >= 0; i --) {
if(f[a][i] != f[b][i]) {
a = f[a][i];
b = f[b][i];
}
}
return f[a][0];
}
inline void work(int x, int fa) {
for(Re i = head[x], to; i; i = a[i].next) {
to = a[i].to;
if(to == fa)continue;
work(to, x);
root[x] = merge(root[x], root[to], 1, Inf);
}
ans[x] = vol[root[x]];
if(sum[root[x]] == 0)ans[x] = 0;
}
signed main () {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
in(n);
in(m);
for(int i = 1, x ,y; i < n; i ++) {
in(x);
in(y);
Qian(x, y);
Qian(y, x);
}
dfs(1, 0);
//cout << "?????????";
for(int i = 1, x, y, z; i <= m; i ++) {
in(x);
in(y);
in(z);
int lca = LCA(x, y);
insert(root[x], 1, Inf, z, 1);
insert(root[y], 1, Inf, z, 1);
insert(root[lca], 1, Inf, z, -1);
insert(root[f[lca][0]], 1, Inf, z, -1);
}
work(1, 0);
for(Re i = 1; i <= n; i ++) {
cout << ans[i] << endl;
}
return 0;
}
愿你在冷铁卷刃之前,得以窥见天光

浙公网安备 33010602011771号