P4556 [Vani有约会] 雨天的尾巴 /【模板】线段树合并
题目背景
深绘里一直很讨厌雨天。
灼热的天气穿透了前半个夏天,后来一场大雨和随之而来的洪水,浇灭了一切。
虽然深绘里家乡的小村落对洪水有着顽固的抵抗力,但也倒了几座老房子,几棵老树被连根拔起,以及田地里的粮食被弄得一片狼藉。
无奈的深绘里和村民们只好等待救济粮来维生。
不过救济粮的发放方式很特别。
题目描述
首先村落里的一共有n座房屋,并形成一个树状结构。然后救济粮分m次发放,每次选择两个房屋 (x,y),然后对于x到y的路径上(含x和y)每座房子里发放一袋z类型的救济粮。
然后深绘里想知道,当所有的救济粮发放完毕后,每座房子里存放的最多的是哪种救济粮。
简要概括
N个点,形成一个树状结构。有M次发放,每次选择两个点x,y对于x到y的路径上(含x,y)每个点发一袋Z类型的物品。完成所有发放后,每个点存放最多的是哪种物品。
样例输入
5 3
1 2
3 1
3 4
5 3
2 3 3
1 5 2
3 3 3
样例输出
2
3
3
0
2
注释很全,请耐心看一看,写了好久哒
#include<bits/stdc++.h> using namespace std; #define lson t[rt].ls #define rson t[rt].rs const int maxn=1e5+10; struct stu { int ls,rs;//左右子树 int num;//物品个数的众数 int w;//个数最多的物品是哪种 }t[maxn*80]; int n,m,f[maxn][30],dep[maxn]; int h[maxn],to[maxn*2],nxt[maxn*2],tot; int root[maxn],segtot,ans[maxn]; bool vis[maxn]; void add(int x,int y)//加边 { to[++tot]=y; nxt[tot]=h[x]; h[x]=tot; } void dfs1(int x,int fa) { vis[x]=true;//遍历过哩记录一下 dep[x]=dep[fa]+1;//比父节点深度+1 for(int j=1;(1<<j)<=n;j++)//倍增lca { f[x][j]=f[f[x][j-1]][j-1]; } for(int i=h[x];i;i=nxt[i]) { if(!vis[to[i]])//根据刚刚的记录,如果遍历过了就跳过 //因为是双向边,所以需要判一下 { f[to[i]][0]=x;//子节点to[i]向上跳一个深度就是父节点 //0是2的0次方也就是1 dfs1(to[i],x);//继续遍历下一个 } } } int lca(int x,int y) { if(dep[x]<dep[y]) swap(x,y);//反正只是求最近公共祖先,所以换一下也没关系 //这样可以直接认为x深度就是比y小的,不用分两种情况了 int d=dep[x]-dep[y];//记录两个节点深度的差值,先跳到同一深度,再一起向上跳 for(int i=0;i<30;i++) { if(d&(1<<i)) x=f[x][i];//如果跳i深度还是比d小那就能跳,否则就不能跳了 //否则就跳过了 } if(x==y) return x;//这里判的是如果正好一个节点是另一个的祖先 for(int i=29;i>=0;i--) { if(f[x][i]!=f[y][i])//这里只能判!=,要是==的话,可能不是最近公共祖先 //可能会跳过因为是从最大开始的 { x=f[x][i];//都同时向上跳一样的深度 y=f[y][i]; } if(f[x][0]==f[y][0]) return f[x][0];//如果两个不同子节点的父节点相同 //说明已经到了要求的父节点了直接就能结束循环了 } } void pushup(int rt)//向上更新 { if(t[lson].num>=t[rson].num)//这就很显而易见了吧,哪个大就取哪个子树的信息喽 { t[rt].num=t[lson].num;//把num和w全都附上最大值 t[rt].w=t[lson].w; } else { t[rt].num=t[rson].num; t[rt].w=t[rson].w; } } void update(int &rt,int l,int r,int pos,int val)//这里解释一下l和r虽然代指区间 //但是实际就是物品种类,后面会具体体现,pos代表物品种类,val直接就是1 //相当于这种物品在这个权值线段树中加1 { if(!rt) rt=++segtot;//如果还没有以rt为根的子树,就新建一个叭 //这体现了动态开点的特点 if(l==r)//区间长度为1,表明已经递归到最底层可以开始赋值啦 { t[rt].w=l;//这里用l,r,pos都是一样的 t[rt].num+=val;//这种物品个数加1 return;//别忘了返回 } int mid=(l+r)>>1;//二分向下找pos if(pos<=mid) update(lson,l,mid,pos,val);//这个就是二分查找了吧...? else update(rson,mid+1,r,pos,val); pushup(rt);//向上更新众数和物品种类 } int merge(int ra,int rb,int l,int r)//合并 { if(!ra||!rb) return ra|rb;//如果一个子树w物品为空就返回有值的一个 if(l==r)//如果找到w物品且都不为空 { t[ra].num+=t[rb].num;//将两个物品个数直接相加最后一定>=0 return ra;//返回 } int mid=(l+r)>>1; t[ra].ls=merge(t[ra].ls,t[rb].ls,l,mid);//二分向下更新子节点 t[ra].rs=merge(t[ra].rs,t[rb].rs,mid+1,r); pushup(ra);//向上更新父节点 return ra; } void dfs2(int x)//遍历求答案 { vis[x]=true; for(int i=h[x];i;i=nxt[i]) { if(!vis[to[i]])//又用到哩 { dfs2(to[i]);//不断往下遍历子节点 root[x]=merge(root[x],root[to[i]],1,maxn);//将子树的信息直接合并 //到父节点上,不用考虑别的之前都处理好了 } } if(t[root[x]].num) ans[x]=t[root[x]].w;//如果num不为零就赋给ans啦 } int main() { int x,y,z; scanf("%d%d",&n,&m); for(int i=1;i<n;i++) { scanf("%d%d",&x,&y); add(x,y);//加双向边就不用担心有遍历不到的了,就可以随便取根啦 add(y,x); } dfs1(1,0);//因为是无向图,就随便取一个点当根就好啦,且认为他的父节点为0 memset(vis,false,sizeof(vis));//dfs2还要用先重置一下 for(int i=1;i<=m;i++) { scanf("%d%d%d",&x,&y,&z); int t=lca(x,y);//求最近公共祖先 update(root[x],1,maxn,z,1);//记录的时候只将x和y将w物品个数+1 update(root[y],1,maxn,z,1);//其他先不管,以后再向上合并 update(root[t],1,maxn,z,-1);//记录最近公共祖先w物品-1 //因为合并的时候x和yw都有1个1 update(root[f[t][0]],1,maxn,z,-1);//最近公共祖先的父节点再-1 //合并的时候w本来没发在这个父节点,但合并的时候会合并上两个w //最近公共祖先减了一次,父节点本来第i次没发放w,所以要减两次 //应该能理解吧......语文不好见谅吧QAQ } dfs2(1);//遍历一次求出每个点的众数 for(int i=1;i<=n;i++) printf("%d\n",ans[i]);//依次输出 return 0; }
本代码借鉴于https://www.cnblogs.com/zhagnxinyue/p/18184950
注释全由本蒟蒻手打
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具