To_Heart—题解——CF1632E2

题目链接

link.

题解

闲话

神仙题当然要神仙来做,拜谢 @Rabbit_Mua 神仙的三句话题解!!!包讲懂的那种!!1


首先有一个结论,每一次加边肯定是从 1 1 1 连向其他节点。

这个很好证明,因为我们连边的目的是为了让 d ( v ) d(v) d(v) 变小,如果在 u u u v v v 连边可以让 d ( v ) d(v) d(v) 变小,就说明 d ( u ) + x < d ( v ) d(u)+x<d(v) d(u)+x<d(v),因为 d ( 1 ) < d ( u ) d(1)<d(u) d(1)<d(u),所以 1 1 1 可以让 v v v 变更小。

可以发现,如果所有点的深度经过操作的后小于一个特定值 a n s ans ans ,那么这些点经过操作后的深度一定小于 a n s + 1 ans+1 ans+1。所以说答案有单调性。

答案具有单调性,所以考虑二分。设当前答案为 m i d mid mid,问题就转换为在 O ( 1 ) \mathrm {O(1)} O(1) 的时间内判断是否可以通过加一条长度为 x x x 的边使得所有点到 1 1 1 的距离都小于 m i d mid mid

如果一些点本身的深度就小于 m i d mid mid ,那么我们不用管他们,只需要考虑其他深度大于 m i d mid mid 的点。也许就是下面的图:

也就是红线以下的点都是我们需要处理的。

如果红线以下的任一点 i i i 都满足 d ( i ) ≤ m i d d(i)\leq mid d(i)mid,那么红线下相距最远的两个点也一定满足,手玩一下可以发现这个条件是充要的。所以我们只需要判断能否让红线下距离最远的两个点满足 d ( i ) ≤ m i d d(i)\leq mid d(i)mid 即可。

这里有一个结论,就是红线下距离最远的两个点一定在最长链上。所以我们将 1 1 1 和直径的中点 y y y 连一条长度为 x x x 的边,如果 x + ⌈ d i s t 2 ⌉ ≤ m i d x+\lceil \frac{dist}{2} \rceil \leq mid x+2distmid,那么红线下距离最远的两个点 d ( i ) ≤ m i d d(i)\leq mid d(i)mid 一定成立。

直径可以预处理,我们用两次 D F S \mathrm{DFS} DFS 就好了

红线下点的集合怎么预处理呢?

第一次 D F S \mathrm {DFS} DFS 以后,为了求出直径,我们需要从深度最深的点开始再跑一次 D F S \mathrm {DFS} DFS。因为是深度最深的点,所以第二次 D F S \mathrm{DFS} DFS 的根节点在二分中是最有可能在 m i d mid mid 下方的。而且如果有点在 m i d mid mid 下方,那么这个点一定 m i d mid mid 下方。所以我们距离最远的两个点有一个一定是这个点。那么我们就可以用第二次 D F S \mathrm {DFS} DFS 的结果来表示红点下集合的最长链了。

所以就可以 O ( 1 ) \mathrm {O(1)} O(1) 判断了。

code.

#include<bits/stdc++.h>
using namespace std;

vector<int> v[300005];
int dep[2][300005];
int Max[300005];

void DFS(int x,int fa,int op){
	for(auto y:v[x]) if(y^fa) 
		dep[op][y]=dep[op][x]+1,DFS(y,x,op);
}

int n;

int main(){
	int T;cin>>T;
	while(T--){
		scanf("%d",&n);
		for(int i=1;i<=n;i++) v[i].clear(),Max[i]=0;
		for(int i=1,x,y;i<n;i++) scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
		dep[0][1]=0;DFS(1,0,0);
		
		int rt=1;
		for(int i=1;i<=n;i++) if(dep[0][rt]<dep[0][i]) rt=i;
		dep[1][rt]=0;DFS(rt,0,1);
		
		for(int i=1;i<=n;i++) Max[dep[0][i]]=max(Max[dep[0][i]],dep[1][i]);
		for(int i=n-1;i>=1;i--) Max[i]=max(Max[i],Max[i+1]);
		
		for(int i=1;i<=n;i++){
			int l=1,r=n,ans=0;
			while(l<=r){
				int mid=(l+r)>>1; 
				if(min(dep[0][rt],(1+Max[mid+1])/2+i)<=mid) r=mid-1,ans=mid;
				else l=mid+1; 
			}
			printf("%d ",ans);
		}
		printf("\n");
	}
	return 0;
}
posted @ 2022-07-14 21:28  To_Heart  阅读(4)  评论(0编辑  收藏  举报  来源