CCPC网络选拔赛Round2 Jumping Monkey题解

题意

大概就是给你一棵树,每个点都有自己的点权,能从一个点\(u\)跳到另一个点\(v\)当且仅当\(v\)点的权值是\(u->v\)这条路径中最大的,现在想让你求出从每个点为起点时最多能跳到多少点。

分析

考场上的时候发现最大的点是终点,那么把点按权值排序之后离线搞,但是当时想到了从小到大搞,但是没有想到怎么统计答案,只会统计到每个点的有多少个点,然后就寄了。
考虑正解,发现不管从哪个点开始跳,都可以到达最大的那个节点,并且到达了最大的节点之后不能再继续往下跳下去,那边权最大的那个节点一定是最后再到达的那个点。并且我们不能越过这个最大的点
稍微转化一下,我们发现不能越过这个点权最大的点等价于通过这个点权最大的点把树分成若干颗联通块,然后对每个连通块之间再递归考虑这个过程
但是发现并想不到怎么递归实现,所以就考虑反过来,按点权从小到大建立一棵树,我们发现在这个过程中,由于是从小到大枚举的每一个点,那么如果\(u,v\)两个点不连通,那么说明\(u->v\)这条路径上一定有一个点权大于\(u\)的点,那么根据题目中的规则,就不能从\(u\)跳到\(v\)。但是这里要注意一点(也是考场上卡住我的一点),就是不要像是普通的离线做法那样,一边插入点一边求答案,那样的话你会发现答案你只能求出每个点到达我们枚举这个点的次数,而不能求出以某个点为起点的答案。所以我们在枚举的时候,枚举到某一个点,把他作为他相连的已经枚举过的点权比他小的点的连通块的根,这个很好理解,因为按照点权从小到达排完序之后,这个连通块的所有点都可以直接到达他。同时,在最后建出的这棵树上,我们发现每个点点权比他大的点的深度都比他要小,而以某个点为根节点的子树中的若干个兄弟,因为那个根的阻挡,不能互相到达,这也符合之前的一个点把这个树分成若干个连通块,那么这样之后,其实一个点能到达的所有点就是从某个点开始到根节点的一条链,那么每个点的深度就是每个点最后的答案。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n;
int father[N],dep[N];
vector<int>g[N],tree[N];
void clear(int n){
	for(int i=0;i<=n;i++){
		g[i].clear();
		tree[i].clear();
		dep[i]=0;
	}
	return;
}
int find(int x){
	if(father[x]!=x)
		father[x]=find(father[x]);
	return father[x];
}
void dfs(int o,int fa){
	dep[o]=dep[fa]+1;
	for(int i=0;i<tree[o].size();i++){
		int j=tree[o][i];
		if(j==fa)
			continue;
		dfs(j,o);
	}
	return;
}
int main(void){
	int T;
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		vector<pair<int,int> >a;
		vector<int>val(n+1,0);
		for(int i=1;i<=n;i++)
			father[i]=i;
		for(int i=1;i<n;i++){
			int u,v;
			scanf("%d%d",&u,&v);
			g[u].push_back(v);
			g[v].push_back(u);	
		}
		for(int i=1;i<=n;i++){
			int x;
			scanf("%d",&x);
			a.push_back(make_pair(x,i));
			val[i]=x;
		}
		sort(a.begin(),a.end());
		for(int i=0;i<n;i++){
			int id=a[i].second;
			for(int j=0;j<g[id].size();j++){
				if(val[g[id][j]]>val[id])
					continue;
				int l=find(id),r=find(g[id][j]);
				father[r]=l;
				tree[l].push_back(r);
			}
		}
		int root=a[n-1].second;
		dfs(root,0);
		for(int i=1;i<=n;i++)
			printf("%d\n",dep[i]);
		clear(n);
	}
	return 0;
} 

心得

最大的心得就是自己菜,而且对离线做法的理解太刻板了,一直都以为在离线的过程中把最后的答案求出来,其实并非如此,可以只在一次离线过程中将一些性质归并起来,就像在这道题中,先枚举每个点,然后这个点会把所有点权比他小的分成若干个连通块,不同连通块之间不能相互到达,然后我们把这些连通块作为这个枚举到的节点的儿子,就得到了兄弟子树之间不能相互到达的这个结论,那么显然一个点所有能到达的点就是只能向上的一条链,自然的引出每个点的答案就是每个点的深度。

posted @ 2021-10-11 10:42  兮水XiShui  阅读(297)  评论(0)    收藏  举报