[题解]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;
}
posted @ 2024-10-16 21:08  Sinktank  阅读(28)  评论(0编辑  收藏  举报
★CLICK FOR MORE INFO★ TOP-BOTTOM-THEME
Enable/Disable Transition
Copyright © 2023 ~ 2024 Sinktank - 1328312655@qq.com
Illustration from 稲葉曇『リレイアウター/Relayouter/中继输出者』,by ぬくぬくにぎりめし.