题目传送门:https://www.luogu.org/problem/P5666
很早以前就觉得,凡是考树的重心相关的题,到最后都变成一道模拟题。
树的重心有许多优秀的性质,比如:
结论一:记f(node)表示以node为根的最大子树的大小。从无根树上的任意一个点x出发,向相邻的点走一步。假设某个点y的f值比x的小,那么x向y走就相当于向树的重心移动了一步。假设不存在这样的y,那么x就是重心。假设存在f(y)=f(x),那么它们就是树的两个重心。
结论二:设全树为有根树。记g(node)表示node为根的子树的重心。假设son是node的子树中size最大的儿子(重儿子),那么g(node)一定是g(son)的祖先,因此让g(son)向上跳即可。这个很显然。
由结论二可得结论三:g(node)一定在node所在的重链上。
基于上述三条结论,很容易得出一个从任意一点x走到当前全树的重心的快速算法:
x先向上跳,直到跳到一个y,使得y上方的size大于y的最大子树的size,而father(y)并不。要注意的是y可能并不存在,而此时x本身就是我们要找的。
接下来就是沿着father(y)或x的重链向下跳。但这样很麻烦,简单的方法是拿出father(y)/x的重儿子的重心向上跳。
那么对于这道题而言,整棵树砍掉node的子树之后的重心,就是从node的父亲重新向上跳,然后再向下跳(或者计算新的重儿子,从它的重心向上跳)。实际处理起来细节比较多,尤其是可能出现y和father(y)本身就是两个重心之类的极端情况。
然后xjb写,xjb调,就过了。
CODE:
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int maxn=300100;
const int maxl=22;
typedef long long LL;
struct edge
{
int obj;
edge *Next;
} e[maxn<<1];
edge *head[maxn];
int cur;
int fa[maxn][maxl];
int dep[maxn];
int st[maxn];
int ed[maxn];
int Time;
int Size[maxn];
int son1[maxn];
int son2[maxn];
int w[maxn];
LL ans;
int n,t;
void Add(int x,int y)
{
cur++;
e[cur].obj=y;
e[cur].Next=head[x];
head[x]=e+cur;
}
void Update(int son,int node)
{
if (Size[son]>=Size[ son1[node] ])
{
son2[node]=son1[node];
son1[node]=son;
}
else
if (Size[son]>Size[ son2[node] ])
son2[node]=son;
}
int Jump(int node,int sum,int limit)
{
for (int j=maxl-1; j>=0; j--)
{
int x=fa[node][j];
if (dep[x]<limit) continue;
if (sum-Size[x]>=Size[ son1[x] ]) node=x;
}
return node;
}
int Get(int node,int sum)
{
return max(sum-Size[node],Size[ son1[node] ]);
}
void Dfs(int node)
{
st[node]=++Time;
dep[node]=dep[ fa[node][0] ]+1;
for (int j=1; j<maxl; j++) fa[node][j]=fa[ fa[node][j-1] ][j-1];
Size[node]=1;
for (edge *p=head[node]; p; p=p->Next)
{
int son=p->obj;
if (son!=fa[node][0])
{
fa[son][0]=node;
Dfs(son);
Size[node]+=Size[son];
Update(son,node);
}
}
ed[node]=Time;
if (!son1[node]) w[node]=node,ans+=node;
else
{
int x=w[ son1[node] ];
x=Jump(x,Size[node],dep[node]);
if ( x!=node && Get(fa[x][0],Size[node])<Get(x,Size[node]) ) x=fa[x][0];
w[node]=x;
if (node!=1) ans+=x;
if ( node!=1 && x!=node && Get(fa[x][0],Size[node])==Get(x,Size[node]) ) ans+=fa[x][0];
}
}
int Calc(int x,int dec)
{
int y=son1[x];
if ( st[y]<=st[dec] && st[dec]<=ed[y] ) return max(Size[y]-Size[dec],Size[ son2[x] ]);
else return Size[y];
}
int Jump(int node,int sum,int limit,int dec)
{
for (int j=maxl-1; j>=0; j--)
{
int x=fa[node][j];
if (dep[x]<limit) continue;
if (sum-Size[x]>= Calc(x,dec) ) node=x;
}
return node;
}
struct data
{
int u,v;
} a[4];
bool Comp(data x,data y)
{
return x.v<y.v;
}
void Work(int node)
{
LL tp=ans;
int x=fa[node][0];
x=Jump(x,n,1,node);
//if (node==4) printf("%d\n",x);
int y=fa[x][0];
for (int i=0; i<4; i++) a[i].v=100000000;
a[0].u=x;
a[0].v=max(n-Size[x],Calc(x,node));
if (y)
{
a[1].u=y;
a[1].v=max(n-Size[y],Calc(y,node));
}
if ( x!=1 && n-Size[x]> Calc(x,node) ) x=y;
int p=son1[x];
if ( st[p]<=st[node] && st[node]<=ed[p] )
if (!son2[x]) p=0;
else p=son2[x];
if (p)
{
p=w[p];
p=Jump(p,n-Size[node],dep[x]+1);
a[2].u=p;
a[2].v=Get(p,n-Size[node]);
p=fa[p][0];
if (p!=x)
{
a[3].u=p;
a[3].v=Get(p,n-Size[node]);
}
}
sort(a,a+4,Comp);
//if (node==4)
//for (int i=0; i<4; i++) printf("%d %d\n",a[i].u,a[i].v);
ans+=a[0].u;
if (a[0].v==a[1].v) ans+=a[1].u;
//printf("%I64d\n",ans-tp);
}
int main()
{
scanf("%d",&t);
while (t--)
{
scanf("%d",&n);
cur=-1;
for (int i=1; i<=n; i++) head[i]=NULL,son1[i]=son2[i]=0;
for (int i=1; i<n; i++)
{
int x,y;
scanf("%d%d",&x,&y);
Add(x,y);
Add(y,x);
}
Time=0;
ans=0;
Dfs(1);
//printf("%I64d\n",ans);
for (int i=2; i<=n; i++) Work(i);
//for (int i=1; i<=n; i++) printf("%d %d ",son1[i],son2[i]);
//printf("\n");
printf("%lld\n",ans);
}
return 0;
}