【题解】P4556 雨天的尾巴
题面
前言
试图口述讲解但失败的云落
正文
前置芝士:线段树合并
这个是相对来说比较好理解的
对于待合并的两棵线段树,我们递归性地将每个节点合并
比如这道题我们需要求某一个最值对应的种类
所以比较容易想到,用线段树维护四个信息
-
左子树编号
-
右子树编号
-
多种救济粮的最大值
-
救济粮的种类
然后我们动态开点建树
但是这是一个树上的信息维护
并且是路径修改
经典 trick:树上差分
对于每次修改
这个过程用线段树维护
当我们把所有的操作(或者说所有相关修改)都上了线段树组之后,考虑合并信息
合并的过程如下:设待合并的两棵线段树根节点为
从
如果两个结点存在空结点,直接保留有结点的那一个即可
然后如果区间变为单点(即
否则就继续向下递归调用
遍历左子树,右子树,最后将答案上传
Warning: 线段树的合并函数的返回值为 int
为什么捏?返回的是什么?
可以参考 Treap 的合并
当我们把两棵线段树合并之后,我们会有一个新的根结点编号
这个根结点编号用于解决新线段树被调用的情况
比如说这个线段树作为一个新的待合并的树出现,那么根结点的编号就需要被调用
所以 merge
的返回值是 int
回顾一下整个过程:
-
输入,连边
-
倍增 LCA
-
树上差分,线段树维护单点修改
-
修改结束,将树形的线段树组做信息合并
-
最后统计答案
补充:
-
线段树合并的数组空间大小为
,其中 是操作次数(本题中为 ), 是结点数 -
pushup
中不能简单用max
修改 的值,还需要连带修改 的信息,所以直接if
判断
代码
#include<iostream>
#define endl '\n'
#define int long long
using namespace std;
const int maxn=1e5+10;
int n,m;
int head[maxn],tot;
struct Edge{
int to,nxt;
}e[maxn<<1];
int fa[maxn][20],dep[maxn];
int rt[maxn],cnt;
int ans[maxn];
struct Segment_tree{
struct node{
int l,r,sum,col;
}tr[maxn*50];
void pushup(int u){
if(tr[tr[u].l].sum>=tr[tr[u].r].sum){
tr[u].sum=tr[tr[u].l].sum;
tr[u].col=tr[tr[u].l].col;
}else{
tr[u].sum=tr[tr[u].r].sum;
tr[u].col=tr[tr[u].r].col;
}
return;
}
void modify(int &u,int l,int r,int pos,int k){
if(u==0){
u=++cnt;
}
if(l==r){
tr[u].sum+=k;
tr[u].col=pos;
return;
}
int mid=l+r>>1;
if(pos<=mid){
modify(tr[u].l,l,mid,pos,k);
}else{
modify(tr[u].r,mid+1,r,pos,k);
}
pushup(u);
return;
}
int merge(int x,int y,int l,int r){
if(!x||!y){
return x+y;
}
if(l==r){
tr[x].sum+=tr[y].sum;
return x;
}
int mid=l+r>>1;
tr[x].l=merge(tr[x].l,tr[y].l,l,mid);
tr[x].r=merge(tr[x].r,tr[y].r,mid+1,r);
pushup(x);
return x;
}
}Tr;
inline void add(int u,int v){
e[++tot].to=v;
e[tot].nxt=head[u];
head[u]=tot;
return;
}
inline void dfs(int u,int fath){
dep[u]=dep[fath]+1;
fa[u][0]=fath;
for(int i=1;i<=18;i++){
fa[u][i]=fa[fa[u][i-1]][i-1];
}
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==fath){
continue;
}
dfs(v,u);
}
return;
}
inline int lca(int x,int y){
if(dep[x]<dep[y]){
swap(x,y);
}
for(int i=18;i>=0;i--){
if(dep[fa[x][i]]>=dep[y]){
x=fa[x][i];
}
}
if(x==y){
return y;
}
for(int i=18;i>=0;i--){
if(fa[x][i]!=fa[y][i]){
x=fa[x][i];
y=fa[y][i];
}
}
return fa[x][0];
}
inline void solve(int u,int fath){
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==fath){
continue;
}
solve(v,u);
rt[u]=Tr.merge(rt[u],rt[v],1,maxn);
}
ans[u]=(Tr.tr[rt[u]].sum?Tr.tr[rt[u]].col:0);
return;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n-1;i++){
int u,v;
cin>>u>>v;
add(u,v);
add(v,u);
}
dfs(1,0);
while(m--){
int x,y,z;
cin>>x>>y>>z;
Tr.modify(rt[x],1,maxn,z,1);
Tr.modify(rt[y],1,maxn,z,1);
Tr.modify(rt[lca(x,y)],1,maxn,z,-1);
Tr.modify(rt[fa[lca(x,y)][0]],1,maxn,z,-1);
}
solve(1,0);
for(int i=1;i<=n;i++){
cout<<ans[i]<<endl;
}
return 0;
}
后记
完结撒花!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具