题目链接
思路:树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么他们的距离和一样。 那么题目所求的就是每颗子树的重心了。 dfs的时候求出每个子树的重心和最短距离,考虑怎么合并所有子树。 这时候要用到第二个性质了:把一个树添加或删除一个叶子,那么它的重心最多只移动一条边的距离。 那么这样就相当于重心在往上爬,我们可以让重心一直跳,找到根在这个子树上时,最短的距离和。 对所有子树处理一遍就能找到最小距离和所有的重心了。 转移的时候自己推一推贡献即可。
#pragma GCC optimize(2) #pragma GCC optimize(3) #include <bits/stdc++.h> using namespace std; typedef long long LL; const int N = 2e5 + 10; #define fi first #define se second #define pb push_back #define wzh(x) cerr<<#x<<'='<<x<<endl; int n,f[N],sz[N],dep[N]; LL sum[N],need[N]; vector<int>v[N]; vector<int>ans[N]; void dfs(int x,int y,int de=0){ f[x]=y; dep[x]=de; sum[x]=de; sz[x]=1; ans[x].pb(x); for(auto k:v[x])if(k!=y){ dfs(k,x,de+1); sz[x]+=sz[k]; sum[x]+=sum[k]; } LL co=sum[x]-1ll*de*sz[x]; int ro=x; for(auto k:v[x])if(k!=y){ int res=ans[k][0]; LL ne=need[k]; vector<int>tmp; tmp.pb(res); ne+=sum[x]-sum[k]-1ll*(sz[x]-sz[k])*dep[x]; ne+=1ll*(dep[res]-dep[x])*(sz[x]-sz[k]); while(f[res]!=x){ int fa=f[res]; LL re=ne-(sz[x]-sz[k]); re+=sz[res]; re-=(sz[k]-sz[res]); if(re<ne){ tmp.clear(); ne=re; res=fa; tmp.pb(res); }else if(re==ne){ tmp.pb(fa); res=fa; }else break; } if(ne<co){ co=ne; ans[x]=tmp; }else if(ne==co){ for(auto k:tmp)ans[x].pb(k); } } need[x]=co; } int main() { ios::sync_with_stdio(false); cin>>n; for(int i=1;i<n;i++){ int s,t; cin>>s>>t; v[s].pb(t); v[t].pb(s); } dfs(1,0); for(int i=1;i<=n;i++){ int sz=ans[i].size(); sort(ans[i].begin(),ans[i].end()); for(int j=0;j<sz;j++){ cout<<ans[i][j]<<" \n"[j==sz-1]; } } return 0; }