solution:
首先有这样一个结论
一颗树的重心要么是根节点要么在它重儿子的子树里
这个结论十分显然在这里就不证明了
于是我们第一遍(dfs)求出以1节点为根时每个点的重儿子和次重儿子(因为会断掉重儿子的这条边)以及(pr_{u,i})表示从(u)节点开始每次沿重儿子跳(2^i)步跳到的点的位置
对于第二次(dfs),遍历到(u)节点时,考虑其儿子节点(v),计算将((u,v))断掉后的答案
以(v)为根节点的子树重心,直接用先前处理的(pr)倍增跳同时判断(size)即可
而(u)为根节点的树的重心(相当于换根),从1到(u)沿途修改重儿子值即可,倍增方法相同
code:
#include<bits/stdc++.h>
using namespace std;
const int _=3e5+5;
int T,N,Tot;
int Fi[_],Ne[_<<1],To[_<<1];
int Son[_],Sc_Son[_],H[_][20],Sz[_];
long long Ans;
inline int read()
{
int s=0,w=1; char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
for(;isdigit(ch);ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
return s*w;
}
inline void add(int x,int y)
{
Ne[++Tot]=Fi[x];Fi[x]=Tot;To[Tot]=y;
}
inline void pre(int x)
{
H[x][0]=Son[x];
for(int i=1;i<=18;++i)
H[x][i]=H[H[x][i-1]][i-1];
}
void dfs1(int u,int fa)
{
int &su=Sz[u],&s=Son[u],&sc=Sc_Son[u];
su=1;
for(int i=Fi[u];i;i=Ne[i])
{
int v=To[i],&sv=Sz[v];
if(v==fa)continue;
dfs1(v,u);
su+=sv;
if(sv>=Sz[s]){sc=s;s=v;}
else if(sv>Sz[sc])sc=v;
}
pre(u);
}
inline void get_ans(int x)
{
if(!Son[x]){Ans+=x;return;}
int y=x;
for(int i=18;i>=0;--i)
if(Sz[x]-Sz[H[y][i]]<Sz[x]/2)y=H[y][i];
if(Sz[x]-Sz[y]<=Sz[x]/2&&Sz[Son[y]]<=Sz[x]/2)Ans+=y;
y=Son[y];
if(Sz[x]-Sz[y]<=Sz[x]/2&&Sz[Son[y]]<=Sz[x]/2)Ans+=y;
}
void dfs2(int u,int fa)
{
for(int i=Fi[u];i;i=Ne[i])
{
int v=To[i];
if(v==fa)continue;
int &su=Sz[u],&s=Son[u],sv=Sz[v],_su=Sz[u],_s=Son[u];
su=N-sv;
if(v==Son[u])s=Sc_Son[u];
if(fa&&Sz[fa]>Sz[s])s=fa;
pre(u);
get_ans(u),get_ans(v);
dfs2(v,u);
su=_su;s=_s;pre(u);
}
}
int main()
{
T=read();
while(T--)
{
N=read();
Ans=Tot=0;fill(Fi+1,Fi+N+1,0);
for(int i=1;i<N;++i)
{
int u=read(),v=read();
add(u,v),add(v,u);
}
fill(Son+1,Son+N+1,0);
fill(Sc_Son+1,Sc_Son+N+1,0);
dfs1(1,0);dfs2(1,0);
printf("%lld
",Ans);
}
return 0;
}