一棵树,定义(diam(l,r))表示区间([l,r])中的点的直径。求(sum_{l<r} diam(l,r))。
(nle 10^5)
题解有详细证明:https://codeforces.com/blog/entry/85750
先将所有的边拆成两条,中间插个虚点。
定义(C(v,r))表示距离点(v)在(r)以内的点的集合(可以形象地看成一个圆)。定义(Cover(S))表示一个(C(v,r)),满足(Ssubseteq C(v,r)),且(r)最小。显然(r)为直径的一半,(v)为直径中点。
接下来有结论:
对于两个非空点集(S,T),讨论(cover(S),cover(T),cover(Sigcup T))的关系:
- 如果(cover(S)subseteq cover(T)),则(cover(Sigcup T)=cover(T))。反之同理。
- 否则,设(cover(S)=C(v_S,r_S),cover(T)=C(v_T,r_T),cover(Sigcup T)=(V,R)),则(R=frac 12({r_S+r_T+dis(v_S,v_T)})),(V)为(v_S o v_T)方向上距离(R-r_s)的点(即新直径连接两个(v_S,v_T))。
(C(v_1,r_1)subseteq C(v_2,r_2))的判定:(dis(v_1,v_2)le r_2-r_1)(没错和几何上的圆的内含一样)
知道了这个之后都是套路了。
分治。设(c_l(i))表示(cover({i,i+1,dots,mid})),(c_r(j))同理。设(iin [l,mid]),从大往小枚举(i)。对于所有(jin [mid+1,r]),可以分成三段:
- (c_r(j)subseteq c_l(i))。
- (c_r(j))和(c_l(i))真相交。
- (c_l(i)subseteq c_r(j))。
主要问题是处理第二段的(sum_jdis(c_l(i)_v,c_r(j)_v))。考虑(i)左移的时候,第二段相当于一个从右往左的滑动窗口。维护个数据结构支持:加点、删点、求任意一个点(v)到所有之前加的点的距离和。点分树即可。
时间复杂度(O(nlg^2 n))。
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cassert>
#define N 200005
#define ll long long
int n;
struct EDGE{
int to;
EDGE *las;
} e[N*2];
int ne;
EDGE *last[N];
void link(int u,int v){
e[ne]={v,last[u]};
last[u]=e+ne++;
}
namespace Tree{
int fa[N],dep[N],siz[N],hs[N],top[N],ls[N],re[N],cnt;
void dfs1(int x){
siz[x]=1;
for (EDGE *ei=last[x];ei;ei=ei->las)
if (ei->to!=fa[x]){
fa[ei->to]=x;
dep[ei->to]=dep[x]+1;
dfs1(ei->to);
siz[x]+=siz[ei->to];
if (siz[ei->to]>siz[hs[x]])
hs[x]=ei->to;
}
}
void dfs2(int x,int t){
top[x]=t;
ls[++cnt]=x;
re[x]=cnt;
if (hs[x]){
dfs2(hs[x],t);
for (EDGE *ei=last[x];ei;ei=ei->las)
if (ei->to!=fa[x] && ei->to!=hs[x])
dfs2(ei->to,ei->to);
}
}
int LCA(int u,int v){
while (top[u]!=top[v])
if (dep[top[u]]>dep[top[v]])
u=fa[top[u]];
else
v=fa[top[v]];
return dep[u]<dep[v]?u:v;
}
int dis(int u,int v){
return dep[u]+dep[v]-2*dep[LCA(u,v)];
}
int kth(int x,int k){
assert(k<=dep[x]);
while (dep[x]-dep[top[x]]<k){
k-=dep[x]-dep[top[x]]+1;
x=fa[top[x]];
}
return ls[re[x]-k];
}
int find(int u,int v,int k){
int lca=LCA(u,v);
if (k<=dep[u]-dep[lca])
return kth(u,k);
return kth(v,dep[u]+dep[v]-2*dep[lca]-k);
}
void work(){
dfs1(1);
dfs2(1,1);
}
}
namespace CDT{
int vis[N];
int sz[N],all;
int g[N][20],b[N][20];
int bs[N][20];
ll s[N],c[N],ss[N][20];
int getsz(int x,int fa){
sz[x]=1;
for (EDGE *ei=last[x];ei;ei=ei->las)
if (ei->to!=fa && !vis[ei->to]){
getsz(ei->to,x);
sz[x]+=sz[ei->to];
}
}
int getG(int x,int fa){
bool is=(all-sz[x]<=all>>1);
for (EDGE *ei=last[x];ei;ei=ei->las)
if (ei->to!=fa && !vis[ei->to]){
int t=getG(ei->to,x);
if (t) return t;
is&=(sz[ei->to]<=all>>1);
}
return is?x:0;
}
void init(int x,int fa,int d){
for (EDGE *ei=last[x];ei;ei=ei->las)
if (ei->to!=fa && !vis[ei->to]){
g[ei->to][d]=g[x][d]+1;
b[ei->to][d]=b[x][d];
bs[ei->to][d]=(x==b[x][d]?ei->to:bs[x][d]);
init(ei->to,x,d);
}
}
void build(int x,int d){
getsz(x,0),all=sz[x],x=getG(x,0);
g[x][d]=0,b[x][d]=x,bs[x][d]=x,init(x,0,d);
vis[x]=1;
for (EDGE *ei=last[x];ei;ei=ei->las)
if (!vis[ei->to])
build(ei->to,d+1);
}
void add(int x,int C){
for (int i=0;b[x][i];++i){
c[b[x][i]]+=C;
s[b[x][i]]+=g[x][i]*C;
ss[bs[x][i]][i]+=g[x][i]*C;
}
}
ll query(int x){
ll res=0;
for (int i=0;b[x][i];++i){
res+=c[b[x][i]]*g[x][i]+s[b[x][i]]-ss[bs[x][i]][i];
if (i)
res-=c[b[x][i]]*g[x][i-1];
}
return res;
}
}
struct cir{
int v,r;
};
bool sub(cir s,cir t){
return s.r<=t.r && Tree::dis(s.v,t.v)<=t.r-s.r;
}
cir merge(cir s,cir t){
cir c;
int d=Tree::dis(s.v,t.v);
if (d<=t.r-s.r) return t;
if (d<=s.r-t.r) return s;
c.r=(s.r+t.r+d)/2;
c.v=Tree::find(s.v,t.v,c.r-s.r);
return c;
}
cir c[N];
ll s[N];
ll ans;
void divide(int l,int r){
if (l==r)
return;
int mid=l+r>>1;
c[mid]={mid,0};
c[mid+1]={mid+1,0};
for (int i=mid-1;i>=l;--i)
c[i]={i,0},c[i]=merge(c[i],c[i+1]);
s[mid]=s[mid+1]=0;
for (int i=mid+2;i<=r;++i){
c[i]={i,0},c[i]=merge(c[i],c[i-1]);
s[i]=s[i-1]+c[i].r;
}
int t1=mid+1,t2=mid+1;
for (int i=mid;i>=l;--i){
//[mid+1,t1-1] [t1,t2-1] [t2,r]
while (t2<=r && !sub(c[i],c[t2])){
CDT::add(c[t2].v,1);
t2++;
}
while (t1<=r && t1<t2 && sub(c[t1],c[i])){
CDT::add(c[t1].v,-1);
t1++;
}
ans+=(ll)(t1-1-mid)*c[i].r*2+(s[r]-s[t2-1])*2+(t1<t2?((s[t2-1]-s[t1-1])+(ll)c[i].r*(t2-t1)+CDT::query(c[i].v)):0);
}
for (;t1<t2;++t1)
CDT::add(c[t1].v,-1);
divide(l,mid);
divide(mid+1,r);
}
int main(){
freopen("in.txt","r",stdin);
scanf("%d",&n);
for (int i=1;i<n;++i){
int u,v;
scanf("%d%d",&u,&v);
link(u,n+i),link(n+i,u);
link(v,n+i),link(n+i,v);
}
Tree::work();
CDT::build(1,0);
divide(1,n);
printf("%lld
",ans/2);
return 0;
}