BZOJ4539

4539: [Hnoi2016]树

Time Limit: 40 Sec   Memory Limit: 256 MB
Submit: 510   Solved: 194
[ Submit][ Status][ Discuss]

Description

  小A想做一棵很大的树,但是他手上的材料有限,只好用点小技巧了。开始,小A只有一棵结点数为N的树,结
点的编号为1,2,…,N,其中结点1为根;我们称这颗树为模板树。小A决定通过这棵模板树来构建一颗大树。构建过
程如下:(1)将模板树复制为初始的大树。(2)以下(2.1)(2.2)(2.3)步循环执行M次(2.1)选择两个数字a,b,
其中1<=a<=N,1<=b<=当前大树的结点数。(2.2)将模板树中以结点a为根的子树复制一遍,挂到大树中结点b的下
方(也就是说,模板树中的结点a为根的子树复制到大树中后,将成为大树中结点b的子树)。(2.3)将新加入大树
的结点按照在模板树中编号的顺序重新编号。例如,假设在进行2.2步之前大树有L个结点,模板树中以a为根的子
树共有C个结点,那么新加入模板树的C个结点在大树中的编号将是L+1,L+2,…,L+C;大树中这C个结点编号的大小
顺序和模板树中对应的C个结点的大小顺序是一致的。下面给出一个实例。假设模板树如下图:


根据第(1)步,初始的大树与模板树是相同的。在(2.1)步,假设选择了a=4,b=3。运行(2.2)和(2.3)后,得到新的
大树如下图所示

现在他想问你,树中一些结点对的距离是多少。

Input

  第一行三个整数:N,M,Q,以空格隔开,N表示模板树结点数,M表示第(2)中的循环操作的次数,Q 表示询问数
量。接下来N-1行,每行两个整数 fr,to,表示模板树中的一条树边。再接下来M行,每行两个整数x,to,表示将模
板树中 x 为根的子树复制到大树中成为结点to的子树的一次操作。再接下来Q行,每行两个整数fr,to,表示询问
大树中结点 fr和 to之间的距离是多少。N,M,Q<=100000

Output

  输出Q行,每行一个整数,第 i行是第 i个询问的答案。

Sample Input

5 2 3
1 4
1 3
4 2
4 5
4 3
3 2
6 9
1 8
5 3

Sample Output

6
3
3

HINT

经过两次操作后,大树变成了下图所示的形状:



结点6到9之间经过了6条边,所以距离为6;类似地,结点1到8之间经过了3条边;结点5到3之间也经过了3条边。

Source

对于这个题,我不想发表什么意见了

注意longlong,切记,注意longlong!!!


#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define maxn 100005
using namespace std;
typedef long long ll;
struct node{
	int lc,rc,cnt;
}seg[maxn*20];
struct kuai{
	ll l,r;
	int rt;
	kuai(){}
	kuai(ll l,ll r,int rt):l(l),r(r),rt(rt){}
	int operator<(const kuai& k)const{return r<k.r;}
}op[maxn];
struct edge{
	int r,nxt;
}e[maxn<<1],xe[maxn<<1];
struct data{
	int conn,ra;
	data(){}
	data(int conn,int ra):conn(conn),ra(ra){}
};
int fa[maxn][20],ptr,head[maxn],root[maxn],esz,dep[maxn],dfn[maxn],tot,size[maxn],top,tim[maxn],n,m,q;
ll xdist[maxn][20],dist[maxn],now;
data xfa[maxn][20];
void modify(int rt1,int &rt2,int l,int r,int k){
	rt2=++ptr;
	seg[rt2]=seg[rt1];
	seg[rt2].cnt++;
	if(l==r)return ;
	int mid=l+r>>1;
	if(k<=mid)modify(seg[rt1].lc,seg[rt2].lc,l,mid,k);
	else modify(seg[rt1].rc,seg[rt2].rc,mid+1,r,k);
}
int query(int rt1,int rt2,int l,int r,int k){
	if(l==r)return l;
	int sz=seg[seg[rt2].lc].cnt-seg[seg[rt1].lc].cnt;
	int mid=l+r>>1;
	if(sz>=k)return query(seg[rt1].lc,seg[rt2].lc,l,mid,k);
	else return query(seg[rt1].rc,seg[rt2].rc,mid+1,r,k-sz);
}
int dfs(int u,int f,int d){
	fa[u][0]=f,dist[u]=d;
	dfn[++top]=u,tim[u]=top,size[u]=1;
	for(int i=1;i<20;++i)fa[u][i]=fa[fa[u][i-1]][i-1];
	for(int t=head[u];t;t=e[t].nxt)if(e[t].r!=f)size[u]+=dfs(e[t].r,u,d+1);
	return size[u];
}
int getid(int rt,int k){
	return query(root[tim[rt]-1],root[tim[rt]+size[rt]-1],1,n,k);
}
int getkuai(ll u){
	return lower_bound(op+1,op+tot+1,kuai(0,u,0))-op;
}
void addedge(int u,int v){
	e[++esz].r=v;e[esz].nxt=head[u];head[u]=esz;
}
ll lca(int u,int v){
	ll ans=dist[u]+dist[v];
	if(dist[u]<dist[v])swap(u,v);
	int d=dist[u]-dist[v];
	for(int i=0;d>>i;i++)
		if((d>>i)&1)u=fa[u][i];
	if(u==v)return ans-2*dist[u];
	for(int i=19;i>=0;--i)if(fa[u][i]!=fa[v][i])
		u=fa[u][i],v=fa[v][i];
	u=fa[u][0];
	return ans-2*dist[u];
}
ll solve(ll u,ll v){
	ll ans=0;
	bool flag=0;
	int ku=getkuai(u),kv=getkuai(v);
	if(dep[ku]<dep[kv])swap(ku,kv),swap(u,v);
	
	u=getid(op[ku].rt,u-op[ku].l+1);
	v=getid(op[kv].rt,v-op[kv].l+1);
	if(ku==kv)return lca(u,v);
	
	int d=dep[ku]-dep[kv];
	
	for(int i=0;d>>i;i++)if((d>>i)&1){
		ans+=dist[u]-dist[op[ku].rt];
		u=xfa[ku][i].conn;
		ans+=xdist[ku][i];
		ku=xfa[ku][i].ra;
	}
		
	if(ku==kv)return lca(u,v)+ans;
	ans+=dist[u]-dist[op[ku].rt],u=op[ku].rt;
	ans+=dist[v]-dist[op[kv].rt],v=op[kv].rt;
	
	for(int i=19;i>=0;--i)if(xfa[ku][i].ra!=xfa[kv][i].ra){
		ans+=dist[u]-dist[op[ku].rt],u=op[ku].rt;
		ans+=dist[v]-dist[op[kv].rt],v=op[kv].rt;
		u=xfa[ku][i].conn,v=xfa[kv][i].conn;
		ans+=xdist[ku][i]+xdist[kv][i];
		ku=xfa[ku][i].ra,kv=xfa[kv][i].ra;
		flag=true;
	}
	ans+=2;
	
	if(flag)ans+=dist[u]-dist[op[ku].rt]+dist[v]-dist[op[kv].rt];
	u=xfa[ku][0].conn,v=xfa[kv][0].conn;	
	
	return ans+lca(u,v);
}
int main(){
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1,u,v;i<n;++i)
		scanf("%d%d",&u,&v),addedge(u,v),addedge(v,u);
	dfs(1,0,1);
	for(int i=1;i<=n;++i)modify(root[i-1],root[i],1,n,dfn[i]);
	op[++tot]=kuai(1,size[1],1);
	now=size[1];
	ll v;
	int u;
	for(int i=1;i<=m;++i){
		scanf("%d%lld",&u,&v);
		int ra=getkuai(v);
		int conn=getid(op[ra].rt,v-op[ra].l+1);
		op[++tot]=kuai(now+1,now+size[u],u),now+=size[u];
		xfa[tot][0]=data(conn,ra),xdist[tot][0]=1;
		dep[tot]=dep[ra]+1;
	}
	for(int i=1;i<20;++i)
		for(int j=1;j<=tot;++j){
			xfa[j][i]=xfa[xfa[j][i-1].ra][i-1];
			xdist[j][i]=xdist[j][i-1]+dist[xfa[j][i-1].conn]-dist[op[xfa[j][i-1].ra].rt]+xdist[xfa[j][i-1].ra][i-1];
		}
	for(int i=1;i<=q;++i){
		ll u,v;
		scanf("%lld%lld",&u,&v);
		printf("%lld\n",solve(u,v));
	}
			
}





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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值