【BZOJ 4539】[Hnoi2016]树 倍增lca+主席树

236 篇文章 0 订阅
15 篇文章 0 订阅

其实思想特别简单,首先我们是不可能真的把一颗子树全部弄过去的,所以只能把一个根节点代表一个子树连入新的树中。

1.如何找到新树节点对应的原树节点编号。

对于原来的模板树用主席树维护dfs序,每次先二分出这个节点在哪一次操作是添加的,找到原来的子树根节点,然后用主席树查找区间第k大就好了。

2.新树求距离

两种情况,要么在同一次添加的,直接在原树上做,否则倍增到新树的lca上,计算得到。

虽然思想简单,但是写起来的话。。。坑

1.判断两个点是不是祖孙关系,特殊处理

2.每一次先倍增到同一个块,然后在块上倍增

#include<cstdio>
#include<cstring>
#include<iostream>
#define maxn 100021
#define LL long long
using namespace std;
int n,m,qq,cur[maxn],in[maxn],out[maxn],ca[maxn];
int head[maxn],tot=1,cnt,f[maxn][20],rt[maxn],size_tr,size[maxn];
LL h[maxn],g[maxn][20],ss[maxn];
int ls[maxn*40],rs[maxn*40],sum[maxn*40];
struct edge{int v,next;}e[maxn*2];
void adde(int a,int b){e[tot].v=b,e[tot].next=head[a];head[a]=tot++;}
void dfs(int u,int fa){
	cur[in[u]=++cnt]=u,f[u][0]=fa,h[u]=h[fa]+1,size[u]=1;
	for(int i=1;i<=17;i++)f[u][i]=f[f[u][i-1]][i-1];
	for(int v,i=head[u];i;i=e[i].next){
		if((v=e[i].v)==fa)continue;
		dfs(v,u);size[u]+=size[v];
	}out[u]=cnt;
}
/**************主席树查询第k大***************/
void insert(int x,int& y,int l,int r,int id){
	y=++size_tr;ls[y]=ls[x],rs[y]=rs[x],sum[y]=sum[x]+1;
	if(l==r)return;
	int mid=l+r>>1;
	if(id>mid)insert(rs[x],rs[y],mid+1,r,id);
	else insert(ls[x],ls[y],l,mid,id);
}
int query(int x,int y,int l,int r,int k){
	if(l==r)return l;
	int mid=l+r>>1;
	int s=sum[ls[y]]-sum[ls[x]];
	if(s>=k)return query(ls[x],ls[y],l,mid,k);
	else return query(rs[x],rs[y],mid+1,r,k-s);
}

//新树的处理 
int nn,nf[maxn][20],fl[maxn],nd[maxn];
LL nh[maxn];
int find(LL x){//二分查找现在的编号在哪一个节点 
	int l=0,r=nn;
	while(l<r){
		int mid=l+r+1>>1;
		if(ss[mid]>=x)r=mid-1;
		else l=mid;
	}
	return l+1;
}
int Q(LL x,int nx){//查询现在的编号对应的原来的编号 
	x-=ss[nx-1];
	return query(rt[in[ca[nx]]-1],rt[out[ca[nx]]],1,n,x);
}
void init(int a,LL b){
	nn++;ca[nn]=a;ss[nn]=ss[nn-1]+size[a];
	int B=find(b),lb=Q(b,B);fl[nn]=lb;
	nf[nn][0]=B;nh[nn]=nh[B]+(LL)h[lb]-h[ca[B]]+1;
	nd[nn]=nd[B]+1;
	for(int i=1;i<=17;i++)nf[nn][i]=nf[nf[nn][i-1]][i-1];
}

void build(){
	int a;LL b;ca[++nn]=1;ss[nn]=size[1];nh[nn]=nd[nn]=1;
	while(m--){
		scanf("%d%lld",&a,&b);
		init(a,b);
	}
}

int lca1(int a,int b){
	if(h[a]>h[b])swap(a,b);
	for(int i=17;i>=0;i--)if(h[f[b][i]]>=h[a])b=f[b][i];
	if(a==b)return a;
	for(int i=17;i>=0;i--){
		if(f[a][i]==f[b][i])continue;
		a=f[a][i],b=f[b][i];
	}return f[a][0];
}
int lca2(int a,int b){
	if(nd[a]>nd[b])swap(a,b);
	for(int i=17;i>=0;i--)if(nd[nf[b][i]]>=nd[a])b=nf[b][i];
	if(a==b)return a;
	for(int i=17;i>=0;i--){
		if(nf[a][i]==nf[b][i])continue;
		a=nf[a][i],b=nf[b][i];
	}return nf[a][0];
}

void solve(){
	LL a,b;int x,y,dx,la,lb,dy;
	while(qq--){
		scanf("%lld%lld",&a,&b);
		x=find(a),y=find(b);//大树的编号 
		la=Q(a,x),lb=Q(b,y);//原来编号 
		if(x==y){
			int g=lca1(la,lb);
			printf("%d\n",h[la]+h[lb]-2*h[g]);
		}else{
			int g=lca2(x,y);bool ok=false;
			dx=x,dy=y;
			for(int i=17;i>=0;i--){
				if(nd[nf[dx][i]]>nd[g])dx=nf[dx][i];
				if(nd[nf[dy][i]]>nd[g])dy=nf[dy][i];
			}
			if(x==g)dx=la;else dx=fl[dx];
			if(y==g)dy=lb;else dy=fl[dy];
			
			int gg=lca1(dx,dy);
			if(nh[x]>nh[y]){
				swap(x,y),swap(la,lb);swap(dx,dy);
			}
			if(g==x&&la==gg)ok=true;
			
			if(!ok)
			printf("%lld\n",nh[x]+h[la]-h[ca[x]]+h[lb]-h[ca[y]]+nh[y]-2*(nh[g]+h[gg]-h[ca[g]]));
			else printf("%lld\n",h[lb]-h[ca[y]]+nh[y]-(nh[x]+h[la]-h[ca[x]]));
		}
	}
}

int main(){
	scanf("%d%d%d",&n,&m,&qq);
	for(int a,b,i=1;i<n;i++){
		scanf("%d%d",&a,&b);
		adde(a,b),adde(b,a);
	}dfs(1,0);
	for(int i=1;i<=n;i++)insert(rt[i-1],rt[i],1,n,cur[i]);
	build();solve();
	return 0;
} 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值