P9352 [JOI 2023 Final] 训猫 / Cat Exercise

本篇题解可能有点长,但是非常详细

本题算法


DP+LCA+DSU

题意


一棵树,有 \(n\) 个点,点上有权值 \(H_i\),所有的 \(H\) 形成一个 \(1\)\(n\) 的排列。有一个操作,你可以将某一个点删掉其本身和对应的连边,如果你选择了你当前的点,则你必须要走到你能走到的最大高度的点。求你能走的最长距离。

题解


首先可以发现,当你选了你当前的点 \(u\),你就必须进入一颗以 \(u\) 的其中一个儿子 \(v\) 为根的子树中,而且你去了之后去不了以 \(u\) 的另一个儿子为根的子树,因为 \(u\) 被毁掉了。所以有暴力递归以 \(u\) 的每一个儿子为根的子树,找到其中的子树中的最大高度节点,并从 \(u\) 移动到此节点,答案加上移动的距离,再将这棵子树截取下来,以子树中的最大高度节点为根重复以上操作,直到不能走了。把你递归到的所有答案取最大值。时间复杂度 \(O(n^2)\)

此时可以自己想一想暴力的时间的瓶颈在哪里,如何优化?实在想不通了就可以看一下正解。

接下来考虑正解。首先我们可以看出时间的瓶颈在于查找子树中的最大值,这个操作占用了我们很多时间。考虑优化,这个时候就有一个东西叫做 并查集,它可以在大约常数时间内解决联通快中的同类操作,例如:最大值、求和。而本题我们就需要求最大值,这个时候并查集就派上了用场,但是我们知道,并查集加很简单,但删很麻烦。所以考虑反着做,从最小值往大的走,相当于将路径翻转,这样我们就只用加了。

好的,此时的思路已经结束大半了,可以再思考一下此时如何加入DP。当然也可以不想。

我们将所有节点按高度从小往大排。接着我们令 \(dp_u\) 为从最小点 \(1\) 走到 \(u\) 的最大答案,因为从小向大走,所以此时只加入节点 \(u\) 向小于自身的节点的连边,令当前连边为 \(u\)\(v\) ( \(v<u\) ),容易转移 \(dp_u=\max(dp_u,dp_{mx_v}+dis(mx_v,u))\) 。其中 \(mx_v\) 代表目前与 \(v\) 相连的最大高度。为何这样转移,因为这样意思就相当于你目前在节点 \(u\),然后将 \(u\) 删掉,走到 \(mx_v\),这样的路径就多出了一个 \(dis(mx_v,u)\) 然后再以 \(mx_v\) 这个节点走,所以是 \(dp_{mx_v}+dis(mx_v,u)\)。然后 \(mx_v\) 可以通过并查集来解决,遍历到 \(u\) 就加入与 \(u\) 相连的且比 \(u\) 小的边,而 \(dis(mx_v,u)\) 即可用 LCA 解决。

代码


码风不好,请谅解

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,fx[200005],mx[200005],dis[200005],dp[200005][25],f[200005],rd[200005];
struct node{
	int h,id;
}a[200005];
bool cmp(node l,node r){
	return l.h<r.h;
}
vector<int> v[200005];
void dfs(int u,int fa){
	dis[u]=dis[fa]+1;
	dp[u][0]=fa;
	int len=v[u].size();
	for(int i=0;i<len;i++){
		if(v[u][i]==fa)continue;
		dfs(v[u][i],u);
	}
} 
int LCA(int x,int y){
	if(dis[x]>dis[y]){
		swap(x,y);
	}
	for(int i=21;i>=0;i--){
		if(dis[dp[y][i]]<dis[x]) continue;
		y=dp[y][i];
	}
	if(x==y)return x;
	for(int i=21;i>=0;i--){
		if(dp[x][i]==dp[y][i])continue;
		x=dp[x][i];
		y=dp[y][i];
	}
	return dp[x][0];
}
int find(int x){
	return fx[x]==x?x:fx[x]=find(fx[x]);
}
signed main(){
	cin>>n;
	int rt=0,idd=0;
	for(int i=1;i<=n;i++){
		cin>>a[i].h;
		if(rt<a[i].h)rt=a[i].h,idd=i;
		a[i].id=i;
		mx[i]=fx[i]=i;
	}
	sort(a+1,a+1+n,cmp);
	for(int i=1;i<=n;i++){
		rd[a[i].id]=i;
	}
	for(int i=1;i<n;i++){
		int l,r;
		cin>>l>>r;
		l=rd[l];
		r=rd[r];
		v[l].push_back(r);
		v[r].push_back(l);
	}
	dfs(rd[idd],0);
	for(int j=1;j<=(long long)(log(n)/log(2));j++){
		for(int i=1;i<=n;i++){
			dp[i][j]=dp[dp[i][j-1]][j-1];
		}
	}
	for(int i=1;i<=n;i++){
		int len=v[i].size();
		for(int j=0;j<len;j++){
			if(v[i][j]>i)continue;
			int kl=find(v[i][j]),kr=find(i);
			f[i]=max(f[i],f[mx[kl]]+dis[mx[kl]]+dis[i]-dis[LCA(mx[kl],i)]*2);
			if(kl!=kr){
				fx[kl]=kr;
				mx[kr]=max(mx[kr],mx[kl]);
			}
		}
	}
	cout<<f[n];
}
posted @ 2025-08-28 21:09  yxbb  阅读(13)  评论(0)    收藏  举报