[HNOI2016] 树

28 篇文章 0 订阅
24 篇文章 0 订阅
P4055[Hnoi2016 day1]树
时间限制 : - MS   空间限制 : 165536 KB 
评测说明 : 2s
问题描述

小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)后,得到新的
大树如下图所示

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

输入格式

第一行三个整数: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

输出格式

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

样例输入

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

样例输出

6
3
3
















提示

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

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


先膜一发湖南的神题,这个题还是厉害,做了一晚上。

分析:

1.每一次新增的子树缩点,视为缩到这一坨点的根节点处

2.缩点后构建新图,点与点之间边的权值为这两坨点的根节点的距离

3.还必须处理出相连的两坨点到底是哪两个原图中的点相连,不然2.中的距离你怎么算?

   处理这个需要用到可持久化线段树,求区间第k小。至于如何转化为区间第k小的:先对原树求dfs序,显然每一个子树对应dfs序上连续一段。现在给你一个新树上的点x,很容易用二分查找找到属于哪一个缩了之后的点,由于新树上点的编号连续且编号大小顺序对应原树上 的大小顺序,那么x剪掉之前的每一坨点的个数得到数是什么呢?就是这个点在其所在的坨(缩了的一坨点)中的标号的排名k,就转化为了主席树求区间第k大

4.现在我们来看询问。假设给定两个点x,y。

   若x,y属于同一坨点,显然直接输出他们在原树上的最短距离

   若不属于同一坨,在缩了点的新树上找其所在坨之间的距离,加上其自身与其所在坨的根节点的距离。但这还没完,因为找缩了点后的lca那个地方的距离是不对的,两边均是加的直接到那一坨根节点的距离,那么减掉这一部分,又由于可以用之前的预处理得到,路径穿入穿出这一坨经过的是那两个点,再加上这两个点在原树上的最短距离即可。

5.代码写的还是比较浅显易懂,静下心来看一会儿就懂了

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
#include<vector>
#include<cmath>
#define ll long long
using namespace std;
const int inf=0x3f3f3f3f;
template <typename T>
inline void _read(T& x){
    char t=getchar();bool sign=true;
    while(t<'0'||t>'9'){if(t=='-')sign=false;t=getchar();}
    for(x=0;t>='0'&&t<='9';t=getchar())x=x*10+t-'0';
    if(!sign)x=-x;
}
int n,m,q,tot,clk;
int dfn[100005];
int SIZE[5000005],ls[5000005],rs[5000005],root[100005],Root[100005];
int id[100005],R[100005];
void insert(int pre,int &p,int l,int r,int x){  
    p=++tot;SIZE[p]=SIZE[pre]+1;
    if(l==r)return;
    int mid=(l+r)>>1;
    ls[p]=ls[pre];rs[p]=rs[pre];
    if(x<=mid)insert(ls[pre],ls[p],l,mid,x);  
    else insert(rs[pre],rs[p],mid+1,r,x);  
}  
void insert(int id,int v){insert(Root[id-1],Root[id],1,n,v);/*cout<<"insert:"<<id<<" v:"<<v<<endl;*/} 
int query(int pre,int p,int l,int r,int k){  
    if(l==r)return l;
    int mid=(l+r)>>1;
    if(SIZE[ls[p]]-SIZE[ls[pre]]>=k)return query(ls[pre],ls[p],l,mid,k);  
    else return query(rs[pre],rs[p],mid+1,r,k-(SIZE[ls[p]]-SIZE[ls[pre]]));  
}  
int query(int x,int y,int k){return query(Root[x-1],Root[y],1,n,k);}  
struct line{
	int from,to,len;
	line(){}
	line(int x,int y,int z){from=x;to=y;len=z;}
};
struct graph{
	line edge[200005];
	int last[100005],_next[200005];
	int fa[100005][20],dep[100005];
	ll dis[100005],size[100005];
	int e,vistime;
	void init(){
		e=vistime=0;
		memset(fa,0,sizeof(fa));
		memset(last,0,sizeof(last));
		memset(_next,0,sizeof(_next));
	}
	void add_edge(int x,int y,int z){
		edge[++e]=line(x,y,z);
		_next[e]=last[x];
		last[x]=e;
	}
	void dfs(int x){
		//cout<<"dfs:"<<x<<endl;
		for(int i=1;i<=18;i++)fa[x][i]=fa[fa[x][i-1]][i-1];
		for(int i=last[x];i;i=_next[i]){
			int v=edge[i].to;
			//cout<<"v:"<<v<<endl;
			if(v==fa[x][0])continue;
			fa[v][0]=x;dep[v]=dep[x]+1;dis[v]=dis[x]+edge[i].len;
			dfs(v);
		}
	}
	void dfs2(int x){
		//cout<<"dfs2:"<<x<<endl;
		size[x]=1;id[x]=++clk;insert(clk,x);
		for(int i=last[x];i;i=_next[i]){
			int v=edge[i].to;
			//cout<<"v:"<<v<<endl;
			if(v==fa[x][0])continue;
			dfs2(v);
			size[x]+=size[v];
		}
		R[x]=clk;
	}
	void go_up(int& x,int step){
		for(int i=18;i>=0;i--){
			if(step&(1<<i))x=fa[x][i];
		}
	}
	int lca(int x,int y){
		if(dep[x]<dep[y])swap(x,y);
		go_up(x,dep[x]-dep[y]);
		if(x==y)return x;
		for(int i=18;i>=0;i--){
			if(fa[x][i]!=fa[y][i]){
				x=fa[x][i];y=fa[y][i];
			}
		}
		return fa[x][0];
	}
	ll dist(int x,int y){
		return dis[x]+dis[y]-2ll*dis[lca(x,y)];
	}
	int up(int x,int y){
		go_up(x,dep[x]-dep[y]-1);
		return x;
	}
};
graph ori,newg;
ll sum=0,cnt[200005];
int from[100005];
int pos;
int getid(ll x){
	return lower_bound(cnt+1,cnt+1+pos,x)-cnt;
}
void query(ll a,ll b){  
    int ida=getid(a),roota=root[ida],aa=query(id[roota],R[roota],a-cnt[ida-1]);  
    int idb=getid(b),rootb=root[idb],bb=query(id[rootb],R[rootb],b-cnt[idb-1]);  
    int lca=newg.lca(ida,idb);  
    if(ida==idb){
    	printf("%lld\n",ori.dist(aa,bb));
		return;
	} 
    ll res=newg.dist(ida,idb)+ori.dis[aa]-ori.dis[roota]+ori.dis[bb]-ori.dis[rootb];  
    if(ida==lca){
        int frb=from[newg.up(idb,lca)];  
        res-=ori.dis[aa]+ori.dis[frb]-ori.dist(aa,frb)-2*ori.dis[roota];  
    }  
    else if(idb==lca){
        int fra=from[newg.up(ida,lca)];
        res-=ori.dis[bb]+ori.dis[fra]-ori.dist(bb,fra)-2*ori.dis[rootb];  
    }  
    else{  
        int fra=from[newg.up(ida,lca)];  
        int frb=from[newg.up(idb,lca)];  
        res-=ori.dis[fra]+ori.dis[frb]-ori.dist(fra,frb)-2*ori.dis[root[lca]];  
    }  
    printf("%lld\n",res);  
} 
int main(){
	ori.init();
	newg.init();
	int i,j,k;
	cin>>n>>m>>q;
	for(i=1;i<n;i++){
		int x,y;
		_read(x);_read(y);
		ori.add_edge(x,y,1);
		ori.add_edge(y,x,1);
	}
	ori.dfs(1);
	ori.dfs2(1);
	sum=n;cnt[1]=n;root[1]=pos=1;
	for(i=2;i<=m+1;i++){
		ll x,y;
		_read(x);_read(y);
		int ID=getid(y),ROOT=root[ID];
		root[i]=x;pos=i;from[i]=query(id[ROOT],R[ROOT],y-cnt[ID-1]);
		newg.add_edge(i,ID,ori.dis[from[i]]-ori.dis[ROOT]+1);
		newg.add_edge(ID,i,ori.dis[from[i]]-ori.dis[ROOT]+1);
		sum+=ori.size[x];cnt[i]=sum;
	}
	newg.dfs(1);
	for(i=1;i<=q;i++){
		ll x,y;
		_read(x);_read(y);
		query(x,y);
	}
}


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值