线段树的分裂和合并(加强版)
上一篇文章讲了线段树分裂和合并的模板题,下面加强一下理解
题目链接:https://www.luogu.com.cn/problem/P4556
深绘里一直很讨厌雨天。
灼热的天气穿透了前半个夏天,后来一场大雨和随之而来的洪水,浇灭了一切。
虽然深绘里家乡的小村落对洪水有着顽固的抵抗力,但也倒了几座老房子,几棵老树被连根拔起,以及田地里的粮食被弄得一片狼藉。
无奈的深绘里和村民们只好等待救济粮来维生。
不过救济粮的发放方式很特别。
题目描述
首先村落里的一共有 n 座房屋,并形成一个树状结构。然后救济粮分 m 次发放,每次选择两个房屋 (x, y),然后对于 x 到 y 的路径上(含 x 和 y)每座房子里发放一袋 z 类型的救济粮。
然后深绘里想知道,当所有的救济粮发放完毕后,每座房子里存放的最多的是哪种救济粮。
输入格式
输入的第一行是两个用空格隔开的正整数,分别代表房屋的个数 n 和救济粮发放的次数 m。
第 2 到 第 n 行,每行有两个用空格隔开的整数 a, b,代表存在一条连接房屋 a 和 b 的边。
第 (n + 1) 到第 (n+m) 行,每行有三个用空格隔开的整数 x, y, z,代表一次救济粮的发放是从 x 到 y 路径上的每栋房子发放了一袋 z 类型的救济粮。
输出格式
输出 n 行,每行一个整数,第 i 行的整数代表 ii 号房屋存放最多的救济粮的种类,如果有多种救济粮都是存放最多的,输出种类编号最小的一种。
如果某座房屋没有救济粮,则输出 0。
这一道题主要算法有最近公共祖先,线段树合并,其次还有树上差分的思想
主要题意:有一个树形的村庄,每次操作会向x到y的路径的每一户村民发放一袋种类为z的救济粮,最后要求每一户村民家中的数量最多的救济粮的编号,如果数量相同,输出编号较小的
做法:向x到y路径的上每一户村民发放救济粮,可以利用树上差分,给x和y节点的线段树的z救济粮数量加一,x和y的最近公共祖先的线段树的救济粮减1,最近公共祖先的父节点减1,
最后dfs一遍,回溯的时候将子节点的线段树合并到父节点的线段树上去,维护区间最大值即可
那么为什么可以这样做呢,最后可以达到将x到y路径上的每一个点都加上1的效果。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 100010, M = N * 2;
int e[M], ne[M], h[N], idx;
int top[N], f[N], son[N], depth[N], sz[N];
int n, m, timestamp, tot;
int root[N], ans[N];
struct Node{
int l, r;
PII val;
}tr[N * 70];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
void dfs1(int u, int fa)
{
depth[u] = depth[fa] + 1, f[u] = fa, sz[u] = 1;
int mxsz = 0;
for(int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if(j == fa) continue;
dfs1(j, u);
sz[u] += sz[j];
if(mxsz < sz[j])
{
mxsz = sz[j];
son[u] = j;
}
}
}
void dfs2(int u, int t)
{
top[u] = t;
if(son[u]) dfs2(son[u], t);
for(int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if(j == f[u] || j == son[u]) continue;
dfs2(j, j);
}
}
int lca(int x,int y) //这里我用的是树链刨分求lca,当然也可以用倍增
{
while(top[x] != top[y])
{
if(depth[top[x]] < depth[top[y]]) swap(x, y);
x = f[top[x]];
}
return depth[x] < depth[y] ? x : y;
}
PII max(PII& x, PII& y)
{
if(x.first > y.first) return x;
else if(x.first == y.first) return x.second < y.second ? x : y;
else return y;
}
inline void pushup(int k) { tr[k].val = max(tr[tr[k].l].val, tr[tr[k].r].val); }
void modify(int &k, int l,int r,int p,int x)
{
if(!k) k= ++ tot;
if(l==r)
{
tr[k].val.first += x;
tr[k].val.second = p;
return;
}
int m = (l+r)>>1;
if(p<=m) modify(tr[k].l, l, m, p, x);
else modify(tr[k].r, m+1, r, p, x);
pushup(k);
}
void merge(int &x,int y,int l=1,int r=N)
{
if(!(x&&y))
x|=y;
else if(l==r)
tr[x].val.first += tr[y].val.first;
else
{
int m = (l+r)>>1;
merge(tr[x].l, tr[y].l, l, m);
merge(tr[x].r, tr[y].r, m+1, r);
pushup(x);
}
}
void dfs(int u)
{
for(int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if(j == f[u]) continue;
dfs(j);
merge(root[u], root[j]);
}
if(tr[root[u]].val.first) ans[u] = tr[root[u]].val.second;
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
for(int i = 1; i < n; i ++ )
{
int a, b;
cin >> a >> b;
add(a, b), add(b, a);
}
dfs1(1, 0);
dfs2(1, 0);
while(m -- )
{
int x, y, z;
cin >> x >> y >> z;
int anc = lca(x, y);
modify(root[x], 1, N, z, 1);
modify(root[y], 1, N, z, 1);
modify(root[anc], 1, N, z, -1);
modify(root[f[anc]], 1, N, z, -1);
}
dfs(1);
for(int i = 1; i <= n; i ++ ) cout << ans[i] << endl;
return 0;
}