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


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

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

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

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

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

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

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

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

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

    在这里插入图片描述

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

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

    边分治是如下的过程:

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

    u u u 的各个儿子的子树大小为 s 1 , ⋯   , s D − 1 s_1,\cdots,s_{D-1} s1,,sD1(不足 D − 1 D-1 D1 个儿子用 0 0 0 补齐),那么显然对于任意 i i i 满足 s i ≤ n − s i s_i\leq n-s_i sinsi,进一步地,应满足 ( n − s i ) − s i ≥ S − ( n − S ) (n-s_i)-s_i\geq S-(n-S) (nsi)siS(nS),得到 S + s i ≤ n S+s_i\leq n S+sin

    将这 D − 1 D-1 D1不等式加起来,得到 ( D − 1 ) S + ∑ s i ≤ ( D − 1 ) n (D-1)S+\sum s_i\leq (D-1)n (D1)S+si(D1)n,即 D S − 1 ≤ ( D − 1 ) n DS-1\leq (D-1)n DS1(D1)n,那么 S ≤ D − 1 D n + 1 S\leq\frac{D-1}{D}n+1 SDD1n+1

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

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

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

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

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

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

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

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

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

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

    #include
    
    #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;
    }
    
  • 相关阅读:
    微信小程序会议OA系统其他页面
    利用决策树找出最优特征组合
    动捕设备VDSuit Full便携式动作捕捉设备,帮你轻松打破次元壁
    【计算机视觉(CV)】基于全连接网络实现宝石分类
    启动服务报错:Command line is too long Shorten command line for xxx or also for Spri
    本地传奇架设详细教程
    初识Jenkins
    支付系统 — 支付路由
    操作系统学习笔记9 | 内存的换入和换出
    机器学习_10、集成学习-随机森林
  • 原文地址:https://blog.csdn.net/ez_lcw/article/details/127101234