CF1458F Range Diameter Sum
一棵树,定义\(diam(l,r)\)表示区间\([l,r]\)中的点的直径。求\(\sum_{l<r} diam(l,r)\)。
\(n\le 10^5\)
题解有详细证明:https://codeforces.com/blog/entry/85750
先将所有的边拆成两条,中间插个虚点。
定义\(C(v,r)\)表示距离点\(v\)在\(r\)以内的点的集合(可以形象地看成一个圆)。定义\(Cover(S)\)表示一个\(C(v,r)\),满足\(S\subseteq C(v,r)\),且\(r\)最小。显然\(r\)为直径的一半,\(v\)为直径中点。
接下来有结论:
对于两个非空点集\(S,T\),讨论\(cover(S),cover(T),cover(S\bigcup T)\)的关系:
- 如果\(cover(S)\subseteq cover(T)\),则\(cover(S\bigcup T)=cover(T)\)。反之同理。
- 否则,设\(cover(S)=C(v_S,r_S),cover(T)=C(v_T,r_T),cover(S\bigcup T)=(V,R)\),则\(R=\frac 12({r_S+r_T+dis(v_S,v_T)})\),\(V\)为\(v_S\to 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)\)同理。设\(i\in [l,mid]\),从大往小枚举\(i\)。对于所有\(j\in [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(n\lg^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\n",ans/2);
return 0;
}