[Vani有约会] 雨天的尾巴 /【模板】线段树合并
题目背景
深绘里一直很讨厌雨天。
灼热的天气穿透了前半个夏天,后来一场大雨和随之而来的洪水,浇灭了一切。
虽然深绘里家乡的小村落对洪水有着顽固的抵抗力,但也倒了几座老房子,几棵老树被连根拔起,以及田地里的粮食被弄得一片狼藉。
无奈的深绘里和村民们只好等待救济粮来维生。
不过救济粮的发放方式很特别。
题目描述
首先村落里的一共有 n n n 座房屋,并形成一个树状结构。然后救济粮分 m m m 次发放,每次选择两个房屋 ( x , y ) (x, y) (x,y),然后对于 x x x 到 y y y 的路径上(含 x x x 和 y y y)每座房子里发放一袋 z z z 类型的救济粮。
然后深绘里想知道,当所有的救济粮发放完毕后,每座房子里存放的最多的是哪种救济粮。
输入格式
输入的第一行是两个用空格隔开的正整数,分别代表房屋的个数 n n n 和救济粮发放的次数 m m m。
第 2 2 2 到 第 n n n 行,每行有两个用空格隔开的整数 a , b a, b a,b,代表存在一条连接房屋 a a a 和 b b b 的边。
第 ( n + 1 ) (n + 1) (n+1) 到第 ( n + m ) (n + m) (n+m) 行,每行有三个用空格隔开的整数 x , y , z x, y, z x,y,z,代表一次救济粮的发放是从 x x x 到 y y y 路径上的每栋房子发放了一袋 z z z 类型的救济粮。
输出格式
输出 n n n 行,每行一个整数,第 i i i 行的整数代表 i i i 号房屋存放最多的救济粮的种类,如果有多种救济粮都是存放最多的,输出种类编号最小的一种。
如果某座房屋没有救济粮,则输出 0 0 0。
样例 #1
样例输入 #1
5 3
1 2
3 1
3 4
5 3
2 3 3
1 5 2
3 3 3
样例输出 #1
2
3
3
0
2
提示
- 对于 20 % 20\% 20% 的数据,保证 n , m ≤ 100 n, m \leq 100 n,m≤100。
- 对于 50 % 50\% 50% 的数据,保证 n , m ≤ 2 × 1 0 3 n, m \leq 2 \times 10^3 n,m≤2×103。
- 对于 100 % 100\% 100% 测试数据,保证 1 ≤ n , m ≤ 1 0 5 1 \leq n, m \leq 10^5 1≤n,m≤105, 1 ≤ a , b , x , y ≤ n 1 \leq a,b,x,y \leq n 1≤a,b,x,y≤n, 1 ≤ z ≤ 1 0 5 1 \leq z \leq 10^5 1≤z≤105。
思路分析
线段树合并在我看来其实就是树上前缀和,只不过它不在局限于点权、边权的求和,而是值域树的求和。基本上的结构是这样的:
每个节点(房屋)都对应一个值域线段树,值域线段树表征该节点存放救济粮的数量。
如果对于一条路径从起点到终点对于救济粮依次 +1 时一次操作最劣是 O ( n l o g N ) , N 为 值 域 O(nlogN),N为值域 O(nlogN),N为值域 ;很明显时间复杂度不满足条件,因此我们需要用到树上差分与前缀和来处理。
当我们在路径 2 ~ 3 发放一个 3 类型的救济粮时,我们就以树上差分的思想,在节点 2、3 的值域线段树中的 3 对应的区间 +1,在 L c a ( 2 , 3 ) 、 f a t h e r L c a ( 2 , 3 ) Lca(2,3)、{father}_{Lca(2,3)} Lca(2,3)、fatherLca(2,3) 所对应的区间 -1即可,如图。
此时单次发放救济粮的时间复杂度优化为了 O ( l o g N ) O(logN) O(logN) ,总时间复杂度为 O ( m l o g N ) O(mlogN) O(mlogN)。
当然,有差分就得有前缀和,某节点的救济粮类型对应的值域线段树应为其子树差分数组之和。
由此,我们需要引出线段树合并,当我们合并两棵线段树时,其实就是将其对应的节点权值求和并传给父节点(pushup)。
很明显,本题要输出的是每个节点对应的值域线段树中权值最大的第一个位置。
因此我们构建一个这样的结构体:
cpp
struct Tree
{
int l, r, sum, col;//左儿子节点、右儿子节点、权值最大值、救济粮类型
};
因此,我们的pushup函数需要给父节点的值应该是子树权值的最大值,以及对应的救济粮类型
cpp
void pushup(int u)
{
int l = tr[u].l, r = tr[u].r;
if (tr[l].sum >= tr[r].sum)
{
tr[u].sum = tr[l].sum;
tr[u].col = tr[l].col;
}
else
{
tr[u].sum = tr[r].sum;
tr[u].col = tr[r].col;
}
}
对一个权值线段树修改某个救济粮类型:
cpp
void change(int &p, int l, int r, int typ, int k)
{
if (!p)
p = ++idx;
if (l == r)
{
tr[p].sum += k;//修改叶子节点
tr[p].col = typ;
return;
}
int mid = l + r >> 1;
if (typ <= mid)
change(tr[p].l, l, mid, typ, k);
else
change(tr[p].r, mid + 1, r, typ, k);
pushup(p); //往上修改
}
对两线段树的合并:
cpp
int merge(int a, int b, int l, int r) //将b的值合并到a
{
if (!a || !b)
return a + b;//当节点a或b为空时,返回非空的节点
if (l == r)
{
tr[a].sum += tr[b].sum; //修改叶子节点
return a;
}
int mid = l + r >> 1;
tr[a].l = merge(tr[a].l, tr[b].l, l, mid); //合并两线段树的左子树
tr[a].r = merge(tr[a].r, tr[b].r, mid + 1, r); //合并两线段树的右子树
pushup(a); //往上修改
return a;
}
树的遍历:
cpp
void Dfs2(int u, int fa)
{
for (auto v : G[u])
{
if (v == fa)
continue;
Dfs2(v, u);
root[u] = merge(root[u], root[v], 1, 100000);//将子树对应的权值线段树合并到根节点
}
ans[u] = tr[root[u]].sum == 0 ? 0 : tr[root[u]].col;//存储答案
}
##代码:
cpp
#include <bits/stdc++.h>
using namespace std;
#define all(x) x.begin(), x.end()
#define bit1(x) __builtin_popcount(x)
#define Pqueue priority_queue
#define lc p << 1
#define rc p << 1 | 1
#define IOS ios::sync_with_stdio(false), cin.tie(0);
#define fi first
#define se second
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<ll, ll> PII;
const ll mod = 1000000007;
const ll N = 1e6 + 10;
const ld eps = 1e-9;
const ll inf = 1e18;
const ll P = 131;
const ll dir[8][2] = {1, 0, 0, 1, -1, 0, 0, -1, 1, 1, 1, -1, -1, 1, -1, -1};
struct Tree
{
int l, r, sum, col;
} tr[N * 8];
int root[N], idx;
int f[N][21], dep[N];
int Log[N + 10];
vector<int> G[N];
int n, m, u, v, w, ans[N];
/****以下为LCA代码****/
void Dfs1(int u, int fa)
{
dep[u] = dep[fa] + 1;
f[u][0] = fa;
for (int i = 1; i < 21; i++)
f[u][i] = f[f[u][i - 1]][i - 1];
for (auto v : G[u])
{
if (v == fa)
continue;
Dfs1(v, u);
}
}
//倍增求树上LCA
int lca(int a, int b)
{
if (dep[a] < dep[b])
swap(a, b);
for (int i = 20; i >= 0; i--)
if (dep[f[a][i]] >= dep[b])
a = f[a][i];
if (a == b)
return a;
for (int i = 20; i >= 0; i--)
if (f[a][i] != f[b][i])
a = f[a][i], b = f[b][i];
return f[a][0];
}
/****以上为LCA代码****/
void pushup(int u)
{
int l = tr[u].l, r = tr[u].r;
if (tr[l].sum >= tr[r].sum)
{
tr[u].sum = tr[l].sum;
tr[u].col = tr[l].col;
}
else
{
tr[u].sum = tr[r].sum;
tr[u].col = tr[r].col;
}
}
void change(int &p, int l, int r, int typ, int k)
{
if (!p)
p = ++idx;
if (l == r)
{
tr[p].sum += k;
tr[p].col = typ;
return;
}
int mid = l + r >> 1;
if (typ <= mid)
change(tr[p].l, l, mid, typ, k);
else
change(tr[p].r, mid + 1, r, typ, k);
pushup(p);
}
int merge(int a, int b, int l, int r)
{
if (!a || !b)
return a + b;
if (l == r)
{
tr[a].sum += tr[b].sum;
return a;
}
int mid = l + r >> 1;
tr[a].l = merge(tr[a].l, tr[b].l, l, mid);
tr[a].r = merge(tr[a].r, tr[b].r, mid + 1, r);
pushup(a);
return a;
}
void Dfs2(int u, int fa)
{
for (auto v : G[u])
{
if (v == fa)
continue;
Dfs2(v, u);
root[u] = merge(root[u], root[v], 1, 100000);
}
ans[u] = tr[root[u]].sum == 0 ? 0 : tr[root[u]].col;
}
void solve()
{
cin >> n >> m;
for (int i = 1; i < n; i++)
{
cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
Dfs1(1, 0);
while (m--)
{
cin >> u >> v >> w;
change(root[u], 1, 100000, w, 1);
change(root[v], 1, 100000, w, 1);
int Lca = lca(u, v);
change(root[Lca], 1, 100000, w, -1);
change(root[f[Lca][0]], 1, 100000, w, -1);
}
Dfs2(1, 0);
for (int i = 1; i <= n; i++)
{
cout << ans[i] << "\n";
}
}
int main()
{
for (int i = 2; i <= N; i++)
Log[i] = Log[i / 2] + 1;//初始化Log2数组
IOS int T = 1;
// cin>>T;
while (T--)
solve();
return 0;
}
/*
oxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxox
x o
o _/_/_/_/ _/ x
x _/ o
o _/_/_/_/ _/ _/_/ _/_/ _/_/_/ _/_/ _/_/_/ _/_/ _/_/_/ _/ _/ _/ x
x _/ _/_/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ o
o _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/_/ x
x _/ _/ _/_/ _/ _/ _/ _/_/_/ _/_/ _/ _/ _/ _/ _/ o
o _/ _/ _/ x
x _/ _/_/ _/ o
o x
xoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxo
*/