CF1458F Range Diameter Sum

一棵树,定义(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))的关系:

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

  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(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;
}
原文地址:https://www.cnblogs.com/jz-597/p/14165423.html