bzoj4539 & 洛谷P3248 [HNOI2016]树 主席树+倍增LCA 毒瘤题

题目链接:
bzoj4539
洛谷3248

题目大意:
现在有一棵模板树,有一棵大树。大树一开始为模板树。
现在给出 m m m个操作,每次给出 a , b a,b a,b,然后把 a a a和它的子树接到 b b b上。
q q q次询问,每次给出 u , v u,v u,v,要求输出大树上 u , v u,v u,v的距离。
n , m , q &lt; = 1 0 5 n,m,q&lt;=10^5 n,m,q<=105
具体细节见题面QWQ。

实现方法


如果直接用暴力的方法建树,不难发现,每次最多新增 n n n个点,总共 m m m次操作,复杂度是 O ( n m ) O(nm) O(nm)……
因此要换一种方法建图。
思考把模板树的子树接到大树上的过程,发现后面树的结构和模板树的一部分是一样的。
所以对于每个接上去的子树,记录 l [ i ] , r [ i ] l[i],r[i] l[i],r[i],表示这棵子树编号的左右区间,即 l [ i ] ≤ l[i] \leq l[i]子树内编号 ≤ r [ i ] \leq r[i] r[i]
把大树看作一棵树套树,以样例为例:
在这里插入图片描述
为了方便,这里把普通节点叫做“小节点”,接上去的子树和原来的模板树叫做“大节点”。
因为图中小节点很小,大节点很大qwq
那么图中共有9个小节点,3个大节点。
小节点不用记录信息(记录信息会爆空间),对于每个大节点,记录:
(如果不想看变量(函数)解释,珂以往下拉)

r o o t [ i ] root[i] root[i],表示第 i i i个大节点对应在模板树中的根。
也就是说,把以 u u u为根的子树作为第 i i i个大节点,那么 r o o t [ i ] = u root[i]=u root[i]=u
图中 r o o t [ 1 ] = 1 , r o o t [ 2 ] = 4 , r o o t [ 3 ] = 3. root[1]=1,root[2]=4,root[3]=3. root[1]=1,root[2]=4,root[3]=3.

f a t h e r [ i ] father[i] father[i],表示第 i i i个大节点在树上的小节点父亲。
图中 f a t h e r [ 1 ] = 0 , f a t h e r [ 2 ] = 3 , f a t h e r [ 3 ] = 2. father[1]=0,father[2]=3,father[3]=2. father[1]=0,father[2]=3,father[3]=2. (图中箭头指向的就是 f a t h e r father father

b i g a n c [ i ] [ j ] biganc[i][j] biganc[i][j],表示在大树中,向上跳 2 j 2^j 2j步所到达的大节点。用来求大树上的LCA。
图中 b i g a n c [ 2 ] [ 0 ] = b i g a n c [ 3 ] [ 0 ] = 1 biganc[2][0]=biganc[3][0]=1 biganc[2][0]=biganc[3][0]=1

b i g d i s t [ i ] [ j ] bigdist[i][j] bigdist[i][j],表示在大树中,向上跳 2 j 2^j 2j步,到达 b i g a n c [ i ] [ j ] biganc[i][j] biganc[i][j]的根所跳过的距离。用来计算答案。
图中 b i g d i s t [ 2 ] [ 0 ] = 2 , b i g d i s t [ 3 ] [ 0 ] = 3. bigdist[2][0]=2,bigdist[3][0]=3. bigdist[2][0]=2,bigdist[3][0]=3.

b i g d e e p [ i ] bigdeep[i] bigdeep[i],表示在大树中,大节点 i i i的深度。
图中 b i g d e e p [ 1 ] = 0 , b i g d e e p [ 2 ] = b i g d e e p [ 3 ] = 1. bigdeep[1]=0,bigdeep[2]=bigdeep[3]=1. bigdeep[1]=0,bigdeep[2]=bigdeep[3]=1.

为了求出结果,还要定义几个函数辅助计算:
G e t R o o t ( u ) GetRoot(u) GetRoot(u),表示查询编号为 u u u的小节点在哪一个大节点中。
图中 G e t R o o t ( 2 ) = 1 , G e t R o o t ( 6 ) = 2 , G e t R o o t ( 9 ) = 3. GetRoot(2)=1,GetRoot(6)=2,GetRoot(9)=3. GetRoot(2)=1,GetRoot(6)=2,GetRoot(9)=3.

G e t P o s ( u ) GetPos(u) GetPos(u),表示查询编号为 u u u的小节点在模板树中的位置。
图中 G e t P o s ( 3 ) = 3 , G e t P o s ( 8 ) = 5 , G e t P o s ( 9 ) = 3. GetPos(3)=3,GetPos(8)=5,GetPos(9)=3. GetPos(3)=3,GetPos(8)=5,GetPos(9)=3.

假设现在有一个强劲的 a l b alb alb程序帮你把这些都算好了,那么想怎么求树上点对之间的距离:
在这里插入图片描述
仍然是样例的图,以求8和9之间的距离为例。
首先应该把8和9跳到各自大节点的根。
9已经是根节点,不用管。8会跳到7,此时 a n s = 1 ans=1 ans=1
然后按照倍增求LCA的方法向上让两个大节点跳到同一高度。这里已经在同一高度,不用管。
模拟倍增求LCA的过程,让它们跳到LCA的两个孩子出,这里已经满足条件,不用管。
r t u rtu rtu为跳 u u u过程中大节点的指针, r t v rtv rtv同理。
那么现在 r t u = 3 , r t v = 2 rtu=3,rtv=2 rtu=3,rtv=2
让两端分别向上跳到它们的小节点父亲出,即 r t u = 2. r t v = 3. rtu=2.rtv=3. rtu=2.rtv=3. 此时 a n s = 3. ans=3. ans=3.(又跳了两次)
然后问题就转化为 a n s + d i s t ( 2 , 3 ) ans+dist(2,3) ans+dist(2,3)
在模板树上乱搞一下即可。

但是,这样会有一种情况不满足:
考虑求5,9之间的距离。
如果按上面的方法,把5跳到1,9跳到2,那么问题转化为 a n s + d i s t ( 1 , 2 ) ans+dist(1,2) ans+dist(1,2)。最后得到的答案是5。
但是如果观察一下5,9之间真正的距离,会发现它们之间距离仅为3。
不难发现,边(1,4)是不必要走的,但在上面的走法中多走了2次。
又发现,这种反例当且仅当一个大节点是另一个大节点的祖先时出现。

于是考虑换一下策略:
仍然令 r t u rtu rtu表示 u u u跳到的大节点的指针, r t v rtv rtv表示 v v v跳到的大节点的指针。
不妨设 b i g d e e p [ r t u ] &gt; = b i g d e e p [ r t v ] bigdeep[rtu]&gt;=bigdeep[rtv] bigdeep[rtu]>=bigdeep[rtv](如果不是,swap一下即可)。
如果 b i g d e e p [ r t u ] &gt; b i g d e e p [ r t v ] bigdeep[rtu]&gt;bigdeep[rtv] bigdeep[rtu]>bigdeep[rtv],那么把 r t u rtu rtu跳到深度为 b i g d e e p [ r t v ] + 1 bigdeep[rtv]+1 bigdeep[rtv]+1的位置, r t v rtv rtv不跳,把答案统计入 a n s ans ans
判断:如果 r t u rtu rtu大节点父亲 r t v rtv rtv,那么说明 r t v rtv rtv r t u rtu rtu的祖先。
那么把 r t u rtu rtu跳到小节点父亲 f a t h e r [ r t u ] father[rtu] father[rtu]处,然后求一下它与 v v v之间的距离,加到 a n s ans ans里面即可。
如果 r t v rtv rtv不为 r t u rtu rtu的祖先,那么再让 r t u rtu rtu跳到它的大节点父亲,记录一下答案,然后按照之前的方法倍增计算即可。

实现细节

1. 1. 1.大树内节点的距离要转化为模板树内节点的距离(显然)。
2. 2. 2.找小节点在模板树里的编号时,暴力做肯定是不可取的。
这里考虑找出它所在的大节点的根,然后发现大节点内编号顺序是确定的。
那么转化成一个区间第 k k k小问题,用主席树乱搞即可。
3. 3. 3.查询第 u u u个点在哪个大节点内,由于知道了大节点内的子节点编号范围,所以二分查找一下就珂以了qwq。
4. 4. 4.注意 l o n g long long l o n g long long ! ! !

毒瘤代码

#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<math.h>
#include<vector>
#define re register int
#define rl register ll
using namespace std;
typedef long long ll;
ll read() {
	rl x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=10*x+ch-'0';
		ch=getchar();
	}
	return x*f;
}
namespace I_Love {

const int Size=100005;
const int LOG=19;
int n,m,q,cnt,head[Size];
ll deep[Size];
struct Edge {
	int v,next;
	ll t;
} w[Size<<1];
void AddEdge(int u,int v) {
	w[++cnt].v=v;
	w[cnt].next=head[u];
	head[u]=cnt;
}
int tim,dfn[Size],out[Size],order[Size],anc[Size][LOG];
ll siz[Size];
void dfs(int x,int fa) {
	deep[x]=deep[fa]+1;
	order[++tim]=x;
	dfn[x]=tim;
	anc[x][0]=fa;
	for(re i=1; i<LOG; i++) {
		anc[x][i]=anc[anc[x][i-1]][i-1];
	}
	siz[x]=1;
	for(int i=head[x]; i; i=w[i].next) {
		int nxt=w[i].v;
		if(nxt!=fa) {
			dfs(nxt,x);
			siz[x]+=siz[nxt];
		}
	}
	out[x]=tim;
}
int LCA(int u,int v) {
	if(deep[u]<deep[v])	swap(u,v);
	for(re i=18; i>=0; i--) {
		if(deep[anc[u][i]]>=deep[v]) {
			u=anc[u][i];
		}
	}
	if(u==v)	return u;
	for(re i=18; i>=0; i--) {
		if(anc[u][i]!=anc[v][i]) {
			u=anc[u][i];
			v=anc[v][i];
		}
	}
	return anc[u][0];
}
//主席树查询区间第k小,找小节点在模板树里的编号 
int tot,T[Size];
int ls[Size*LOG],rs[Size*LOG],sum[Size*LOG];
int update(int pre,int l,int r,int v) {
	int rt=++tot;
	ls[rt]=ls[pre]; rs[rt]=rs[pre];
	sum[rt]=sum[pre]+1;
	if(l<r) {
		int mid=(l+r)>>1;
		if(v<=mid) {
			ls[rt]=update(ls[pre],l,mid,v);
		} else {
			rs[rt]=update(rs[pre],mid+1,r,v);
		}
	}
	return rt;
}
int query(int u,int v,int l,int r,int k) {
	if(l==r)	return l;
	int mid=(l+r)>>1,x=sum[ls[v]]-sum[ls[u]];
	if(k<=x)	return query(ls[u],ls[v],l,mid,k);
	return query(rs[u],rs[v],mid+1,r,k-x);
}
//大树的信息 
ll father[Size],root[Size],biganc[Size][LOG],bigdist[Size][LOG];
int bigcnt,bighead[Size];
ll num,l[Size],r[Size];
//num表示当前大节点个数,[l[i],r[i]]表示第i个大节点对应的区间 
Edge bigw[Size<<1];
int GetRoot(ll u) {
	//查询第u个小节点在哪一个大节点中 
	int pos=upper_bound(l+1,l+1+num,u)-l-1;
	if(l[pos]<=u && r[pos]>=u) {
		return pos;
	} else if(l[pos+1]<=u && r[pos+1]>=u) {
		return pos+1;
	} else {
		return pos-1;
	}
}
int GetPos(ll u) {
	//查询第u个小节点在模板树中的位置 
	int bigrt=GetRoot(u);
	int rt=root[bigrt];
	//rt的子树范围:[dfn[rt],out[rt]]
	//u是第u-l[bigrt]+1小 
	return query(T[dfn[rt]-1],T[out[rt]],1,n,u-l[bigrt]+1);
}
void AddBigEdge(ll u,ll v,ll fa) {
	//u,v表示大树中的大节点,fa表示v在大树中的小节点父亲 
	//加一条大树边 
	bigw[++bigcnt].v=v;
	//边权为fa的deep-root的deep 
	bigw[bigcnt].t=deep[GetPos(fa)]-deep[root[u]]+1;
	bigw[bigcnt].next=bighead[u];
	bighead[u]=bigcnt;
	father[v]=fa;
}
ll bigdeep[Size];
inline ll Dist(int u,int v) {
	return deep[u]+deep[v]-(deep[LCA(u,v)]<<1);
}
inline ll GetBigFather(ll u) {
	//返回u的大节点的根在模板树中的编号 
	return root[GetRoot(u)];
}
ll GetDist(ll u,ll v) {
	ll rtu=GetRoot(u);
	ll rtv=GetRoot(v);
	if(rtu==rtv) {
		return Dist(GetPos(u),GetPos(v));
	}
	if(bigdeep[rtu]<bigdeep[rtv]) {
		swap(u,v);
		swap(rtu,rtv);
	}
	ll ans=deep[GetPos(u)]-deep[root[rtu]];
	//先让rtu跳到比rtv深度多1的位置 
	ll tmpu=rtu,tmpv=rtv;
	for(re i=18; i>=0; i--) {
		if(bigdeep[biganc[rtu][i]]>bigdeep[rtv]) {
			ans+=bigdist[rtu][i];
			rtu=biganc[rtu][i];
		}
	}
	if(biganc[rtu][0]==rtv) {
		//v是u的祖先 
		ll fa=father[rtu];		//取rtu在大树中的小节点父亲 
		return ans+Dist(GetPos(fa),GetPos(v))+1;
	}
	//v不是u的祖先 
	if(bigdeep[rtu]>bigdeep[rtv]) {
		ans+=bigdist[rtu][0];
		rtu=biganc[rtu][0];
	}
	ans+=deep[GetPos(v)]-deep[root[rtv]];
	for(re i=18; i>=0; i--) {
		if(biganc[rtu][i]!=biganc[rtv][i]) {
			ans+=bigdist[rtu][i]+bigdist[rtv][i];
			rtu=biganc[rtu][i];
			rtv=biganc[rtv][i];
		}
	}
	ans+=2;		//跳到lca
	rtu=father[rtu];
	rtv=father[rtv];
	return ans+Dist(GetPos(rtu),GetPos(rtv));
}
void Fujibayashi_Ryou() {
//	freopen("tree13.in","r",stdin);
//	freopen("WA.txt","w",stdout);
	n=read();
	m=read();
	q=read();
	for(re i=1; i<n; i++) {
		int u=read();
		int v=read();
		AddEdge(u,v);
		AddEdge(v,u);
	}
	dfs(1,0);
	root[1]=1;
	for(re i=1; i<=n; i++) {
		T[i]=update(T[i-1],1,n,order[i]);
	}
	l[num=1]=1,r[1]=n;
	for(re i=1; i<=m; i++) {
		//把模板树的u复制到v 
		ll u=read();
		ll v=read();
		num++;
		l[num]=r[num-1]+1;
		r[num]=l[num]+siz[u]-1;
		root[num]=u;
		int rt=GetRoot(v);
		father[num]=v;
		bigdeep[num]=bigdeep[rt]+1;
		biganc[num][0]=rt;
		bigdist[num][0]=deep[GetPos(v)]-deep[root[rt]]+1;
		for(re j=1; j<=16; j++) {
			biganc[num][j]=biganc[biganc[num][j-1]][j-1];
			bigdist[num][j]=bigdist[num][j-1]+bigdist[biganc[num][j-1]][j-1];
		}
	}
	while(q--) {
		ll u=read();
		ll v=read();
		printf("%lld\n",GetDist(u,v));
	}
}

}
int main() {
	I_Love::Fujibayashi_Ryou();
	return 0;
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值