[题解]NOIP2018模拟赛 plutotree
题目描述
给定一棵有\(n\)个节点的树,根节点为\(1\),节点\(i\)有权值\(w[i]\)。这棵树非常奇怪,它的每个叶子结点都有一条连向根节点的边。给定\(q\)次询问,每次给定\(u,v\),请计算出一条\(u\)到\(v\)的路径(每条边最多经过\(1\)次),最小化该路径上的点权之和,并在其基础上最大化该路径上的最大点权。
输入的第\(1\)行\(2\)个整数表示\(n\)和\(q\);第\(2\)行有\(n-1\)个正整数,第\(i\)个正整数表示节点\(i+1\)的父节点;第\(3\)行\(n\)个整数,第\(i\)个整数表示\(w[i]\);接下来\(q\)行,每行\(2\)个整数\(u,v\),表示一次查询。
输出\(1\)行\(2\)个整数,表示点权和与最大点权。
- \(1\le n,q\le 10^5\)
- \(0\le w[i]\le 10^9\)。
Sample #1
Input:
5 1 1 2 3 4 413 127 263 869 960 1 5
Output:
1373 960
解题思路
下面所说的“树上”均之未添加特殊边的树上,“距离”均指路径的点权和。
我们分类讨论,答案显然可以分为下面\(4\)种:
- \(u\)和\(v\)经树上的最短路径,在\(lca(u,v)\)处相遇。
- \(u\)从最近的叶子结点到根节点,\(v\)通过树上路径到根节点,在根节点相遇。
- \(u\)通过树上路径到根节点,\(v\)从最近的叶子结点到根节点,在根节点相遇。
- \(u,v\)都通过最近的叶子节点到根节点,在根节点相遇。
虽然上面的部分情况可能走重复边,但这样它就一定不是最优答案了,所以不影响。
所以我们要处理的是:
- \(u\)到\(v\)在树上的距离和路径上最大点权。
- \(u\)到最近叶子节点的距离和路径上最大点权。
第\(1\)条可以直接用倍增求LCA来实现。而第\(2\)条可以通过\(2\)次dfs来实现:
- 用结构体变量
leaf[u]
来表示\(u\)到最近叶子节点的信息(距离\(x\)和最大点权\(y\))。 - 对于叶子结点,初始令
leaf[u]={w[u],w[u]}
;
对于其他节点,初始令leaf[u]={+inf,w[u]}
。 - 第\(1\)次dfs,自下而上更新\(leaf\),即\(leaf[u]\)仅考虑了\(u\)子树中的叶子结点。
搜索完子节点\(i\)后,leaf[u]=upd(leaf[u],{leaf[i].x+w[u],leaf[i].y})
。 - 第\(2\)次dfs,自上而下再更新一次\(leaf\),完成\(leaf\)的计算。
搜索子节点\(i\)之前,leaf[i]=upd(leaf[i],{leaf[u].x+w[i],leaf[u].y})
。 - 其中
upd()
是用于更新答案的,根据定义执行即可:\(x\)不同取最小,\(x\)相同\(y\)取最大。
这样就完成了,对于每次查询更新\(4\)次答案即可,答案和LCA函数也用相同的结构体类型来存储即可,具体见代码。
时间复杂度\(O((n+q)\log n)\)。
Code
点击查看代码
#include<bits/stdc++.h> #define int long long #define N 100010 #define Q 100010 using namespace std; struct res{int x,y;}leaf[N]; void update(res& a,res b){ if(b.x<a.x) a=b; else if(b.y==a.y) a.y=max(a.y,b.y); } int n,q,w[N],dep[N],fa[N][20],maxx[N][20],d[N]; vector<int> G[N]; void dfs1(int u){//计算d,dep,并自下而上更新leaf dep[u]=dep[fa[u][0]]+1; if(G[u].empty()) leaf[u]={w[u],w[u]}; else leaf[u]={LLONG_MAX,w[u]}; for(int i:G[u]){ d[i]+=d[u]; dfs1(i); update(leaf[u],{leaf[i].x+w[u],leaf[i].y}); } } void dfs2(int u){//自上而下再更新一次leaf,完成leaf的计算 for(int i:G[u]){ update(leaf[i],{leaf[u].x+w[i],leaf[u].y}); dfs2(i); } } res LCA(int u,int v){//返回{距离,最大点权} if(dep[u]<dep[v]) swap(u,v); int tmax=max(w[u],w[v]),tdis=d[u]+d[v]; for(int i=19;i>=0;i--) if(dep[fa[u][i]]>=dep[v]) tmax=max(tmax,maxx[u][i]), u=fa[u][i]; if(u==v) return {tdis-d[u]-d[fa[u][0]],tmax}; for(int i=19;i>=0;i--) if(fa[u][i]!=fa[v][i]) tmax=max(tmax,max(maxx[u][i],maxx[v][i])), u=fa[u][i],v=fa[v][i]; int lca=fa[u][0]; tmax=max(tmax,max(maxx[u][1],maxx[v][1])); return {tdis-d[lca]-d[fa[lca][0]],tmax}; } signed main(){ cin>>n>>q; for(int i=2;i<=n;i++){ cin>>fa[i][0]; G[fa[i][0]].emplace_back(i); } for(int i=1;i<=n;i++){ cin>>w[i]; d[i]=maxx[i][0]=w[i]; } dfs1(1),dfs2(1); for(int j=1;j<=19;j++){ for(int i=1;i<=n;i++){ fa[i][j]=fa[fa[i][j-1]][j-1]; maxx[i][j]=max(maxx[i][j-1],maxx[fa[i][j-1]][j-1]); } } while(q--){ int u,v; cin>>u>>v; res ans=LCA(u,v);//1. u,v在LCA相遇 update(ans,{leaf[u].x+d[v],max(leaf[u].y,maxx[v][19])});//2. u去叶子v去根 update(ans,{leaf[v].x+d[u],max(leaf[v].y,maxx[u][19])});//3. v去叶子u去根 update(ans,{leaf[u].x+leaf[v].x+w[1],max(max(leaf[u].y,leaf[v].y),w[1])});//4. u,v都去叶子 cout<<ans.x<<" "<<ans.y<<"\n"; } return 0; }
变量含义说明:
- \(d[u]\):\(u\)到根节点的距离。
- \(maxx[u][i]\):\(u\)向上\(2^i\)个节点(包括\(u\))的最大权值。
再放一份\(maxx[u][i]\)表示“\(u\)向上\(2^i\)个节点(不包括\(u\))的最大权值”的代码:
点击查看代码
#include<bits/stdc++.h> #define int long long #define N 100010 #define Q 100010 using namespace std; struct res{int x,y;}leaf[N]; void update(res& a,res b){ if(b.x<a.x) a=b; else if(b.y==a.y) a.y=max(a.y,b.y); } int n,q,w[N],dep[N],fa[N][20],maxx[N][20],d[N]; vector<int> G[N]; void dfs1(int u){//计算d,dep,并自下而上更新leaf dep[u]=dep[fa[u][0]]+1; if(G[u].empty()) leaf[u]={w[u],w[u]}; else leaf[u]={LLONG_MAX,w[u]}; for(int i:G[u]){ d[i]+=d[u]; dfs1(i); update(leaf[u],{leaf[i].x+w[u],leaf[i].y}); } } void dfs2(int u){//自上而下再更新一次leaf,完成leaf的计算 for(int i:G[u]){ update(leaf[i],{leaf[u].x+w[i],leaf[u].y}); dfs2(i); } } res LCA(int u,int v){//返回{距离,最大点权} if(dep[u]<dep[v]) swap(u,v); int tmax=max(w[u],w[v]),tdis=d[u]+d[v]; for(int i=19;i>=0;i--) if(dep[fa[u][i]]>=dep[v]) tmax=max(tmax,maxx[u][i]), u=fa[u][i]; if(u==v) return {tdis-d[u]-d[fa[u][0]],tmax}; for(int i=19;i>=0;i--) if(fa[u][i]!=fa[v][i]) tmax=max(tmax,max(maxx[u][i],maxx[v][i])), u=fa[u][i],v=fa[v][i]; int lca=fa[u][0]; tmax=max(tmax,w[lca]); return {tdis-d[lca]-d[fa[lca][0]],tmax}; } signed main(){ cin>>n>>q; for(int i=2;i<=n;i++){ cin>>fa[i][0]; G[fa[i][0]].emplace_back(i); } for(int i=1;i<=n;i++){ cin>>w[i]; d[i]=w[i]; } for(int i=1;i<=n;i++) maxx[i][0]=w[fa[i][0]]; dfs1(1),dfs2(1); for(int j=1;j<=19;j++){ for(int i=1;i<=n;i++){ fa[i][j]=fa[fa[i][j-1]][j-1]; maxx[i][j]=max(maxx[i][j-1],maxx[fa[i][j-1]][j-1]); } } while(q--){ int u,v; cin>>u>>v; res ans=LCA(u,v);//1. u,v在LCA相遇 update(ans,{leaf[u].x+d[v],max(leaf[u].y,max(maxx[v][19],w[v]))});//2. u去叶子v去根 update(ans,{leaf[v].x+d[u],max(leaf[v].y,max(maxx[u][19],w[u]))});//3. v去叶子u去根 update(ans,{leaf[u].x+leaf[v].x+w[1],max(max(leaf[u].y,leaf[v].y),w[1])});//4. u,v都去叶子 cout<<ans.x<<" "<<ans.y<<"\n"; } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效