To_Heart—题解——CF1632E2
题目链接
题解
闲话
神仙题当然要神仙来做,拜谢 @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+⌈2dist⌉≤mid,那么红线下距离最远的两个点 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;
}