[題解]NOIP2018模擬賽 plutotree

Sinktank發表於2024-10-16

題目描述

給定一棵有\(n\)個節點的樹,根節點為\(1\),節點\(i\)有權值\(w[i]\)。這棵樹非常奇怪,它的每個葉子結點都有一條連向根節點的權值為\(0\)的邊。給定\(q\)次詢問,每次給定\(u,v\),請計算出一條\(u\)\(v\)的路徑(每條邊最多經過\(1\)次),最小化該路徑上的點權之和,並在其基礎上最大化該路徑上的最大點權。

輸入的第\(1\)\(2\)個整數表示\(n\)\(q\);第\(2\)行有\(n-1\)個正整數,第\(i\)個正整數表示節點\(i+1\)的父節點是\(i\);第\(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

Output:

1373 960

解題思路

下面所說的“樹上”均之未新增特殊邊的樹上,“距離”均指路徑的點權和。

我們分類討論,答案顯然可以分為下面\(4\)種:

  • \(u\)\(v\)經樹上的最短路徑,在\(lca(u,v)\)處相遇。
  • \(u\)從最近的葉子結點到根節點,\(v\)透過樹上路徑到根節點,在根節點相遇。
  • \(u\)透過樹上路徑到根節點,\(v\)從最近的葉子結點到根節點,在根節點相遇。
  • \(u,v\)都透過最近的葉子節點到根節點,在根節點相遇。

雖然上面的部分情況可能走重複邊,但這樣它就一定不是最優答案了,所以不影響。

所以我們要處理的是:

  • \(u\)\(v\)在樹上的距離和路徑上最大點權。
  • \(u\)到最近葉子節點的距離和路徑上最大點權。

前者可以直接用倍增求LCA來實現。而後者可以透過\(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;
}