前置芝士
也没什么前置知识,会动态开点就好了。
它,原理非常简单。
原理
考虑这么一个问题;
现在有两棵值域线段树,每个节点维护当前节点的代表的权值出现的次数,然后最后问每个数总的出现的数。
很显然,就是两棵线段树上的相同位置相加就好了。
对于普通线段树,这很好实现叭。
将两棵线段树按位相加就好了,然后重新建出一棵线段树来就好了。
但是会发现,这样非常炸空间。
如果值域是 (10 ^ 5) 然后开 (10^5) 个线段树,很显然空间就会起飞 /jk。
但是线段树它有一个变形体------动态开点
因为动态开点建出来的线段树不一定是满的,所以就会出现两种形态不同的线段树合并的情况,而线段树合并够在(O(nlogn)) 解决这个问题的。
具体实现
假设现在是要把 (y) 这个线段树合并到 (x) 这个线段树上。
对两棵树同时 (dfs), (dfs) 的过程判断 (x, y) 两棵线段树的当前节点是否有左儿子,如果都有,继续递归它们的左儿子。
如果 (x) 没有左儿子,但 (y) 有,那就直接把 (x) 左儿子指向 (y) 的左儿子就好了。
其他情况直接返回就好。
到达叶子节点就把 (y) 的信息加到 (x) 上就好了。
显然,这样递归合并的时候,只会遍历两棵线段树相同的部分,所以合并的复杂度为 (O(log n))
模板
P4556 [Vani有约会]雨天的尾巴 /【模板】线段树合并
题目描述
一棵树,每个节点有若种背包,(m) 次操作,每次操作将一条路径上的某种背包内都加一件物品,(m) 次操作后,输出每个节点的哪个背包物品数量最多。
(nleq 10^5)
solution
树上差分。
先暴力一点。
每个点开个 (vector) ,然后每次路径加的时候,差分一下,最后合并的时候直接暴力 (O(z)) 枚举每种背包合并。
发现复杂度瓶颈在合并上。
考虑线段树分治。
对于每个点,按照 (z) 建一棵值域线段树,上面维护该点权值出现的次数,同时维护区间的最大值。
合并的时候按照上述操作合并就好了。
复杂度 (O(nlogn))
/*
work by:Ariel_
Sorce:
Knowledge:
Time:
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
#define ll long long
#define rg register
using namespace std;
const int MAXN = 1e5 + 5;
const int MAXX = 1e5;
int read(){
int x = 0,f = 1; char c = getchar();
while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
return x*f;
}
int Ans[MAXN], root[MAXN];
struct edge{int v, nxt;}e[MAXN << 1];
int E = 1, head[MAXN];
void add_edge(int u, int v) {
e[++E] = (edge){v, head[u]};
head[u] = E;
}
namespace Seg{
#define ls lson[rt]
#define rs rson[rt]
int pre[MAXN << 6], lson[MAXN << 6], rson[MAXN << 6], cnt[MAXN << 6], Max[MAXN << 6], node_num;
int bin[MAXN << 6], top = 0;
void push_up(int rt) {
Max[rt] = cnt[Max[ls]] >= cnt[Max[rs]] ? Max[ls] : Max[rs];
}
void Del(int rt) {
ls = rs = cnt[rt] = Max[rt] = pre[rt] = 0, bin[++top] = rt;
}
void Insert(int &rt, int l, int r, int val, int k) {
if(!rt) rt = top ? bin[top--] : ++node_num;
if(l == r) {
if(l != val) return ;
cnt[rt] += k, Max[rt] = rt, pre[rt] = l;
return ;
}
int mid = (l + r) >> 1;
if(val <= mid) Insert(ls, l, mid, val, k);
else Insert(rs, mid + 1, r, val, k);
push_up(rt);
}
int Query(int rt, int l, int r) {return Max[rt];}
void Merge(int &x, int y, int l, int r) {
if(!x || !y) {x = x + y;return;}
if(l == r) {cnt[x] += cnt[y], Max[x] = x; return ; }
int mid = (l + r) >> 1;
Merge(lson[x], lson[y], l, mid), Merge(rson[x], rson[y], mid + 1, r);
Del(y);
push_up(x);
return ;
}
}
using namespace Seg;
namespace Cut{
int dep[MAXN], top[MAXN], siz[MAXN], fath[MAXN], son[MAXN];
void dfs(int u, int fa) {
dep[u] = dep[fa] + 1, siz[u] = 1, fath[u] = fa;
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].v;
if(v == fa) continue;
dfs(v, u);
siz[u] += siz[v];
if(siz[son[u]] < siz[v]) son[u] = v;
}
}
void dfs2(int x, int tp) {
top[x] = tp;
if(son[x]) dfs2(son[x], tp);
for (int i = head[x]; i; i = e[i].nxt) {
int v = e[i].v;
if(v == son[x] || v == fath[x]) continue;
dfs2(v, v);
}
}
int lca(int x, int y) {
while(top[x] != top[y]) {
(dep[top[x]] < dep[top[y]]) ? y = fath[top[y]] : x = fath[top[x]];
}
if(dep[x] > dep[y]) swap(x, y);
return x;
}
}
int n, m;
void Dfs(int u, int fa) {
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].v;
if(v == fa) continue;
Dfs(v, u);
Seg::Merge(root[u], root[v], 1, MAXX);
}
Ans[u] = Query(root[u], 1, MAXX);
}
using namespace Cut;
int main(){
n = read(), m = read();
for (int i = 1, u, v; i < n; i++) {
u = read(), v = read();
add_edge(u, v), add_edge(v, u);
}
dfs(1, 0), dfs2(1, 1);
for (int i = 1, u, v, z; i <= m; i++) {
u = read(), v = read(), z = read();
Insert(root[u], 1, MAXX, z, 1);
Insert(root[v], 1, MAXX, z, 1);
Insert(root[lca(u, v)], 1, MAXX, z, -1);
if(fath[lca(u, v)]) Insert(root[fath[lca(u, v)]], 1, MAXX, z, -1);
}
Dfs(1, 0);
for (int i = 1; i <= n; i++) {
if(cnt[Ans[i]]) printf("%d
", pre[Ans[i]]);
else puts("0");
}
puts("");
return 0;
}