题解 P5666 【树的重心【民间数据】】
根据重心的性质,这个题明显有
以重心
记
那么
然后考虑去掉子树
记
若
(显然,若
记
删除的子树越大重心移动得越远。各种线性复杂度的方法都行。
注意处理两个重心的情况。
总复杂度
#include <cctype>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define rep(i,x,y) for (int i=x; i<=(y); ++i)
#define repe(i,x) for (edge *i=x; i; i=i->nxt)
int get()
{
char c;
while (!isdigit(c=getchar()));
int k=c-'0';
for (; isdigit(c=getchar()); k=k*10+c-'0');
return k;
}
using namespace std;
typedef long long ll;
const int N=3e5;
int n,root,idx,id[N],s[N],mx[N],fa[N];
ll ans;
struct edge
{
int v;
edge *nxt;
} pool[N*2],*tp,*fst[N];
int dfs0(int x,int fa=0)
{
s[x]=1;
int v,mx=0;
repe(i,fst[x])
if ((v=i->v)!=fa)
{
int t=dfs0(v,x);
if (t)
return t;
s[x]+=s[v],mx=max(mx,s[v]);
}
mx=max(mx,n-s[x]);
return mx<<1<=n? x:0;
}
int dfs1(int x)
{
id[x]=++idx,s[x]=1,mx[x]=0;
int v,g=x;
repe(i,fst[x])
if ((v=i->v)!=fa[x])
{
fa[v]=x;
int t=dfs1(v);
s[x]+=s[v];
if (s[v]>s[mx[x]])
mx[x]=v,g=t;
}
if (x==root)
return x;
while (s[g]<<1<s[x])
g=fa[g];
ans+=s[g]<<1>s[x]? g:g+fa[g];
return g;
}
int a[N],*pa;
int b[N],*pb;
int find(int &g,int n)
{
while (s[mx[g]]<<1>=n)
g=mx[g];
return s[g]<<1>n? g:g+fa[g];
}
int gx[N],gy[N];
void work()
{
tp=pool,memset(fst,0,sizeof(fst)),ans=0;
n=get();
rep(i,2,n)
{
int u=get(),v=get();
*tp=(edge){v,fst[u]},fst[u]=tp++;
*tp=(edge){u,fst[v]},fst[v]=tp++;
}
root=dfs0(1);
idx=fa[root]=0,dfs1(root);
int x=root,y=0;
repe(i,fst[root])
if (i->v!=mx[x] && s[i->v]>s[y])
y=i->v;
int mn=n-s[y]*2,l=id[mx[x]],r=l+s[mx[x]];
rep(k,1,n-1)
gx[k]=find(x,n-k);
gx[n]=0;
fill(gy+1,gy+mn,root);
rep(k,mn,n-1)
gy[k]=find(y,n-k);
rep(i,1,n)
ans+=l<=id[i] && id[i]<r? gy[s[i]]:gx[s[i]];
printf("%lld\n",ans);
}
int main()
{
for (int tc=get(); tc--; work());
return 0;
}