【XSY4746】会议选址(三度化,边分治,点分治)

这种关键点的重心问题,一般有两种思路。

一种是合并:对于两个不交的点集 \(S,T\)\(S\cup T\) 的重心必在 \(S,T\) 重心间的路径上,二分即可,需要数据结构支持 dfn 区间内 \(S\cup T\) 内的点的个数。

使用该做法能将本题做到 \(O(n\log^3n+q\log n)\)

另一种想法是,对于单次询问点集 \(S\),先从任意一个点开始,考虑往当前最大的子树走,直到最大的子树大小小于等于 \(S/2\)

如果用树剖+二分暴躁加速这个过程的话,单次询问还是 \(O(\log^3n)\) 的。

优秀的做法是使用三度化和边分治。我们先一一初步介绍。

三度化的具体作用是:将带权树,在不改变任意两点间距离、任意两点间祖先后代关系的情况下,转化成每个点度数至多为 \(3\) 的点数至多翻倍的树.

但三度化会改变两点间的直接父子关系,以及新增一些原树没有的虚点,这些都是需要注意的。

三度化有两种方法,如下图:

在这里插入图片描述

称原树中的点为旧点,新增的点为虚点,三度化后的树为新树。

其中使用第一种方法,能让新树的深度不超过 \(O(d\log \frac{n}{d})\),其中 \(d,n\) 是原树的深度和大小。但这个性质和本题没有关系。

边分治是如下的过程:

假设当前树大小为 \(n\),每个点的度数不超过 \(D\)。找到一条边使得分出来两棵树的大小相差最小,设找到的边是 \((u,v)\),且 \(u\) 对应的那棵子树的大小更大,设为 \(S\)

\(u\) 的各个儿子的子树大小为 \(s_1,\cdots,s_{D-1}\)(不足 \(D-1\) 个儿子用 \(0\) 补齐),那么显然对于任意 \(i\) 满足 \(s_i\leq n-s_i\),进一步地,应满足 \((n-s_i)-s_i\geq S-(n-S)\),得到 \(S+s_i\leq n\)

将这 \(D-1\) 条不等式加起来,得到 \((D-1)S+\sum s_i\leq (D-1)n\),即 \(DS-1\leq (D-1)n\),那么 \(S\leq\frac{D-1}{D}n+1\)

从而,在将树三度化的基础上,按上述方法找到的边,能使得分成的两棵子树大小都不超过 \(\frac{2}{3}n+1\)

于是边分树的深度是 \(O(\log_{\frac{3}{2}}n)=O\left(\dfrac{\log n}{\log\frac{3}{2}}\right)=O(\log n)\) 级别的。

具体回到这题。我们考虑用如下的方式找原树上若干关键点的重心:对于每一条边 \((a,b)\),若 \(a\) 子树内的关键点个数小于 \(b\) 子树内的关键点个数,那么令这条边的方向是 \(a\to b\);否则,若 \(b\) 子树内的关键点个数小于 \(a\) 子树内的关键点个数,那么令这条边的方向是 \(b\to a\)。若 \(a,b\) 子树内的关键点个数相同,那么令这条边无向。

那么可以证明,最后树上肯定形如:存在一个无向边的连通块,然后其余所有边都指向这个连通块。此时连通块内的任意一个点都可以是这些关键点的重心。

考虑将这个方法作用在新树上:那么原树上一条边 \((fa_a,a)\) 的指向,和新树上 \((fa_a',a)\) 的指向相同。

不妨假设新树上那些关键点的重心在 \(rt\),注意 \(rt\) 可能是虚点。设 \(rt\) 在旧点 \(u\) 在新树上的子树内,但不在 \(u\) 任意一个旧儿子在新树上的子树内。考虑 \((fa_u,u)\) 的指向,由于 \(fa_u'\to u\),那么 \(fa_u\to u\)。对于 \(u\) 的任意旧儿子 \(v\),考虑 \((u,v)\) 的指向,由于 \(v\to fa_v'\),那么 \(v\to u\)。从而,我们证明了 \(u\) 是原树上关键点的重心。

于是我们找出新树上的重心即可。我们从边分树的根开始往下走。每次考虑当前边 \(e\) 的指向。考虑直接数出 \(e\) 两边的关键点个数。首先要数 \(A,B\) 子树内编号在 \([l,r]\) 内的点数,可以通过 dfn 序+可持久化做到 \(O(n\log n)-O(\log n)\)。而对于 \(A,B\) 外的那些点,它们在边分树上对应不超过 \(O(\log n)\) 棵子树,且这些子树内的关键点个数我们也可以在走下来的过程中顺便记录,于是我们只需要知道当前这些子树每一棵属于 \(A\) 还是 \(B\) 即可:这其实很简单,因为这些子树肯定整体都在 \(e\) 的一边,所以随便选取一个代表点,利用 dfn 序判断它在 \(e\) 的哪一边即可。

于是,我们单次可以 \(O(\log^2n)\) 找到一个区间的关键点的重心。

再离线下来用点分治求出在原树上对应的重心到所有点的距离,可以做到 \(O((n+q)\log n)\)

从而,我们在 \(O(n\log n+q\log^2n)\) 的时间内解决这个问题。

#include<bits/stdc++.h>

#define N 100010
#define ll long long

using namespace std;

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

int n,Q;

vector<int> e[N];

namespace P2
{
	struct query{int u,id,coef;};
	vector<query> q[N];
}

namespace P1
{
	#define lc(u) ch[u][0]
	#define rc(u) ch[u][1]
	const int N1=N<<1,N2=N1<<1;
	int n1,cnt=1,head[N1],to[N1<<1],nxt[N1<<1];
	int dfx,fa[N1],in[N1],out[N1],belong[N1];
	void adde(int u,int v)
	{
		to[++cnt]=v,nxt[cnt]=head[u],head[u]=cnt;
		to[++cnt]=u,nxt[cnt]=head[v],head[v]=cnt;
	}
	void D3(int u,int fa)
	{
		vector<int> son;
		for(int v:e[u]) if(v!=fa) son.push_back(v);
		function<int(int,int)> dfs=[&](int l,int r)
		{
			if(l==r) return son[l];
			int mid=(l+r)>>1;
			int x=(l==0&&r==(int)son.size()-1?u:++n1);
			belong[x]=u;
			adde(x,dfs(l,mid)),adde(x,dfs(mid+1,r));
			return x;
		};
		belong[u]=u;
		if(!son.empty())
		{
			if(son.size()==1) adde(u,son[0]);
			else dfs(0,son.size()-1);
		}
		for(int v:son) D3(v,u);
	}
	void dfs(int u)
	{
		in[u]=++dfx;
		for(int i=head[u];i;i=nxt[i])
		{
			int v=to[i];
			if(v==fa[u]) continue;
			fa[v]=u,dfs(v);
		}
		out[u]=dfx;
	}
	int n2,Rt,ch[N2][2],eid[N2];
	int idx,id[N1],L[N2],R[N2];
	int nrt,maxn,nn,size[N1];
	bool vis[N1<<1];
	void getsize(int u,int fa)
	{
		size[u]=1;
		for(int i=head[u];i;i=nxt[i])
		{
			int v=to[i];
			if(v==fa||vis[i]) continue;
			getsize(v,u),size[u]+=size[v];
		}
	}
	void getroot(int u,int fa)
	{
		for(int i=head[u];i;i=nxt[i])
		{
			int v=to[i];
			if(v==fa||vis[i]) continue;
			int x=abs(nn-size[v]-size[v]);
			if(x<maxn) maxn=x,nrt=i;
			getroot(v,u);
		}
	}
	int build(int u)
	{
		getsize(u,0);
		if(size[u]==1)
		{
			id[u]=L[u]=R[u]=++idx;
			return u;
		}
		int x=++n2;
		nn=size[u],maxn=INT_MAX,getroot(u,0);
		eid[x]=nrt,vis[nrt]=vis[nrt^1]=1;
		int a=to[nrt],b=to[nrt^1];
		lc(x)=build(a),rc(x)=build(b);
		L[x]=L[lc(x)],R[x]=R[rc(x)];
		return x;
	}
	int rt[N];
	namespace Seg
	{
		const int NN=10000000;
		int node,ch[NN][2],sum[NN];
		void update(int &u,int lst,int l,int r,int x)
		{
			u=++node,lc(u)=lc(lst),rc(u)=rc(lst),sum[u]=sum[lst]+1;
			if(l==r) return;
			int mid=(l+r)>>1;
			if(x<=mid) update(lc(u),lc(lst),l,mid,x);
			else update(rc(u),rc(lst),mid+1,r,x);
		}
		int query(int u,int l,int r,int ql,int qr)
		{
			if(!u) return 0;
			if(ql<=l&&r<=qr) return sum[u];
			int mid=(l+r)>>1,ans=0;
			if(ql<=mid) ans+=query(lc(u),l,mid,ql,qr);
			if(qr>mid) ans+=query(rc(u),mid+1,r,ql,qr);
			return ans;
		}
		int query(int pl,int pr,int ql,int qr)
		{
			return query(rt[pr],1,n1,ql,qr)-query(rt[pl-1],1,n1,ql,qr);
		}
	}
	struct data{int kp,s;};
	bool judge(int p,int a,int b)
	{
		if(fa[a]==b) return in[a]<=in[p]&&in[p]<=out[a];
		return !(in[b]<=in[p]&&in[p]<=out[b]);
	}
	int query(int u,int pl,int pr,vector<data> c)
	{
		if(u<=n1) return u;
		int a=to[eid[u]],b=to[eid[u]^1];
		int sa=Seg::query(pl,pr,L[lc(u)],R[lc(u)]),ta=0;
		int sb=Seg::query(pl,pr,L[rc(u)],R[rc(u)]),tb=0;
		for(auto now:c) (judge(now.kp,a,b)?ta:tb)+=now.s;
		if(sa+ta>sb+tb)
		{
			c.push_back(data{b,sb});
			return query(lc(u),pl,pr,c);
		}
		else
		{
			c.push_back(data{a,sa});
			return query(rc(u),pl,pr,c);
		}
	}
	void main()
	{
		n1=n,D3(1,0); dfs(1);
		n2=n1,Rt=build(1);
		for(int i=1;i<=n;i++)
			Seg::update(rt[i],rt[i-1],1,n1,id[i]);
		for(int i=1;i<=Q;i++)
		{
			int l=read(),r=read();
			int u=belong[query(Rt,l,r,vector<data>())];
			P2::q[r].push_back(P2::query{u,i,1});
			P2::q[l-1].push_back(P2::query{u,i,-1});
		}
	}
	#undef lc
	#undef rc
}

namespace P2
{
	int fa[N];
	int rt,maxn,nn,size[N];
	bool vis[N];
	vector<int> dis[N];
	void getsize(int u,int fa)
	{
		size[u]=1;
		for(int v:e[u]) if(v!=fa&&!vis[v])
			getsize(v,u),size[u]+=size[v];
	}
	void getroot(int u,int fa)
	{
		int nmax=nn-size[u];
		for(int v:e[u]) if(v!=fa&&!vis[v])
			getroot(v,u),nmax=max(nmax,size[v]);
		if(nmax<maxn) maxn=nmax,rt=u;
	}
	void getdis(int u,int fa,int dis,vector<int> *D)
	{
		D[u].push_back(dis);
		for(int v:e[u]) if(v!=fa&&!vis[v])
			getdis(v,u,dis+1,D);
	}
	void build(int u)
	{
		vis[u]=1;
		getdis(u,0,0,dis);
		for(int v:e[u])
		{
			if(vis[v]) continue;
			getsize(v,0);
			nn=size[v],maxn=INT_MAX,getroot(v,0);
			fa[rt]=u,build(rt);
		}
	}
	struct data
	{
		ll sd; int sz;
		data(){}
		data(ll a,int b){sd=a,sz=b;}
		void operator += (data b){sd+=b.sd,sz+=b.sz;}
		data operator - (data b){return data(sd-b.sd,sz-b.sz);}
		ll calc(int d){return 1ll*d*sz+sd;}
	}s1[N],s2[N];
	ll ans[N];
	void add(int x)
	{
		int u=x;
		s1[u]+=data(0,1);
		for(int i=(int)dis[x].size()-2;i>=0;i--,u=fa[u])
			s1[fa[u]]+=data(dis[x][i],1),s2[u]+=data(dis[x][i],1);
	}
	ll query(int x)
	{
		int u=x;
		ll ans=s1[u].sd;
		for(int i=(int)dis[x].size()-2;i>=0;i--,u=fa[u])
			ans+=(s1[fa[u]]-s2[u]).calc(dis[x][i]);
		return ans;
	}
	void main()
	{
		getsize(1,0);
		nn=size[1],maxn=INT_MAX,getroot(1,0);
		build(rt);
		for(int i=1;i<=n;i++)
		{
			add(i);
			for(auto now:q[i]) ans[now.id]+=now.coef*query(now.u);
		}
		for(int i=1;i<=Q;i++)
			printf("%lld\n",ans[i]);
	}
}

int main()
{
	n=read(),Q=read();
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();
		e[u].push_back(v),e[v].push_back(u);
	}
	P1::main();
	P2::main();
	return 0;
}
posted @ 2022-10-31 07:48  ez_lcw  阅读(44)  评论(0编辑  收藏  举报