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)\)的关系:

  1. 如果\(cover(S)\subseteq cover(T)\),则\(cover(S\bigcup T)=cover(T)\)。反之同理。
  2. 否则,设\(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]\),可以分成三段:

  1. \(c_r(j)\subseteq c_l(i)\)
  2. \(c_r(j)\)\(c_l(i)\)真相交。
  3. \(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;
}
posted @ 2020-12-20 21:43  jz_597  阅读(199)  评论(0编辑  收藏  举报