列表

详情


NC20556. [HNOI2016]树

描述

小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个询问的答案。

示例1

输入:

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条边。

原站题解

上次编辑到这里,代码来自缓存 点击恢复默认模板

C++11(clang++ 3.9) 解法, 执行用时: 666ms, 内存消耗: 54636K, 提交时间: 2019-03-09 18:12:20

#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);
	}
}

C++14(g++5.4) 解法, 执行用时: 435ms, 内存消耗: 48580K, 提交时间: 2020-07-10 15:08:21

#include<cstdio>
#include<algorithm>
using namespace std;typedef long long ll;
const int N=1e5+10;int n;int m;int q;
int dfn[N];int nfd[N];int df;int siz[N];int cnt;//开全局变量方便主席树类访问
struct smtree
{
	struct data{int v;int x;}e[2*N];int al[N];int ct;
	inline void add(int u,int v){e[++ct]=(data){v,al[u]};al[u]=ct;}
	ll d[N];bool book[N];int fa[22][N];//倍增板子
	void dfs(int x)
	{
		for(int i=0;fa[i][x];i++){fa[i+1][x]=fa[i][fa[i][x]];}
		book[x]=true;siz[x]=1;dfn[++df]=x;nfd[x]=df;
		for(int i=al[x],v=e[i].v;i;i=e[i].x,v=e[i].v)
		{if(!book[v]){fa[0][v]=x;d[v]=d[x]+1;dfs(v);siz[x]+=siz[v];}}
	}
	inline int lca(int u,int v)//lca板子
	{
		if(d[u]<d[v]){swap(u,v);}int del=d[u]-d[v];
		for(int i=0;del;del>>=1,i++){if(del&1){u=fa[i][u];}}if(u==v){return u;}
		for(int i=20;i>=0;i--){if(fa[i][u]!=fa[i][v]){u=fa[i][u];v=fa[i][v];}}
		return fa[0][u];
	}
	inline ll cd(int u,int v)//求祖先的距离,O(1)
	{ll ret=abs(d[u]-d[v]);return ret;}
	inline ll cdlca(int u,int v,int r)//求lca和公共根的距离
	{ll ret=d[lca(u,v)]-d[r];return ret;}
}st;
struct per_linetree//主席树类
{
	int s[2][22*N];int val[22*N];int root[N];int cnt;
	inline void cson(int p1,int p2,int tp)//静态区间第k大板子没啥好说的
	{s[tp][p2]=++cnt;s[tp^1][p2]=s[tp^1][p1];}
	inline void add(int p1,int p2,int l,int r,int pos)
	{
		val[p2]=val[p1]+1;if(r-l==1){return;}int mid=(l+r)/2;
		if(pos<=mid){cson(p1,p2,0);add(s[0][p1],cnt,l,mid,pos);}
		else {cson(p1,p2,1);add(s[1][p1],cnt,mid,r,pos);}
	}
	inline int kth(int p1,int p2,int l,int r,int rk)
	{
		if(r-l==1){return r;}int mid=(r+l)/2;int nv=val[s[0][p2]]-val[s[0][p1]];
		if(rk<=nv){return kth(s[0][p1],s[0][p2],l,mid,rk);}
		else {return kth(s[1][p1],s[1][p2],mid,r,rk-nv);}
	}
	inline int ckth(int l,int r,int rk){return kth(root[l],root[r],0,n,rk);}
	inline void build()//对dfn建主席树
	{
		root[0]=++cnt;for(int i=1;i<=n;i++)
		{root[i]=++cnt;add(root[i-1],root[i],0,n,dfn[i]);}
	}
	struct nod//用来存bn区间右端点的结构体
	{
		ll v;int root;int bn;
		friend bool operator <(nod a,nod b){return a.v<b.v;}
	}a[N];int ctt;//有序数组
	inline void dtr(ll x,int& root,int& su,int& bu)//这里额外返回一个root,代表bn.root
	{
		nod p=*(lower_bound(a+1,a+ctt+1,(nod){x,0,0}));//先lower_bound
		root=p.root;bu=p.bn;int rk=x+siz[root]-p.v;//写的是第k小板子,所以转一下排名
		su=ckth(nfd[root]-1,nfd[root]+siz[root]-1,rk);//计算sn
	}
	inline void push_nod(int root,int bu)//插入一个新节点,保证数组的有序性
	{a[ctt+1]=(nod){a[ctt].v+siz[root],root,bu};ctt++;}
}plt;
struct bigtree
{
	struct data{int v;int x;ll val;}e[2*N];int al[N];int ct;
	inline void add(int u,int v,ll val){e[++ct]=(data){v,al[u],val};al[u]=ct;}
	bool book[N];int fa[22][N];int d[N];ll dis[N];int att[N];
	void dfs(int x)//倍增板子没啥好说的
	{
		book[x]=true;for(int i=0;fa[i][x];i++){fa[i+1][x]=fa[i][fa[i][x]];}
		for(int i=al[x],v=e[i].v;i;i=e[i].x,v=e[i].v)
		{if(!book[v]){dis[v]=dis[x]+e[i].val;d[v]=d[x]+1;fa[0][v]=x;dfs(v);}}
	}
	inline int lca(int u,int v,int& snu,int& snv)//魔改后的lca,可以传回j1,j2
	{
		if(u==v){return u;}//大力特判u==v
		if(d[u]<d[v]){swap(u,v);swap(snu,snv);}
		int del=d[u]-d[v]-1;//这里u跳到和v差1就好
		for(int i=0;del>0;del>>=1,i++){if(del&1){u=fa[i][u];}}
		if(fa[0][u]==v){snu=att[u];return v;}else if(del!=-1){u=fa[0][u];}//如果不是祖先的话还要补回来
		for(int i=20;i>=0;i--){if(fa[i][u]!=fa[i][v]){u=fa[i][u];v=fa[i][v];}}
		snu=att[u];snv=att[v];return fa[0][u];//如果不是祖先的话直接取att属性就好
	}
	inline ll cd(int u,int v,int p){return dis[u]+dis[v]-2*dis[p];}//因为我们求了lca了,因此直接算就好
}bt;
inline ll cd(ll u,ll v)//正式的计算距离的函数
{
	int r1;int sn1;int bn1;int j1;int r2;int sn2;int bn2;int j2;
	plt.dtr(u,r1,sn1,bn1);plt.dtr(v,r2,sn2,bn2);j1=sn1;j2=sn2;//强行转二元组
	int p=bt.lca(bn1,bn2,j1,j2);int r0=plt.a[p].root;//计算j1.j2,p
	return st.cd(r1,sn1)+st.cd(r2,sn2)-2*st.cdlca(j1,j2,r0)+bt.cd(bn1,bn2,p);//按公式算就行了
}
int main()
{
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<n;i++)
	{int u;int v;scanf("%d%d",&u,&v);st.add(u,v);st.add(v,u);}
	st.dfs(1);plt.build();plt.push_nod(1,++cnt);//记得一开始大树是有一个点的
	for(int i=1;i<=m;i++)
	{
		int x;ll to;scanf("%d%lld",&x,&to);
		int root;int sn;int bn;plt.dtr(to,root,sn,bn);//强行转二元组
		plt.push_nod(x,++cnt);bt.att[cnt]=sn;//插点
		int val=st.cd(sn,root)+1;//计算边权
		bt.add(bn,cnt,val);bt.add(cnt,bn,val);//连边
	}bt.dfs(1);
	for(int i=1;i<=q;i++)
	{
		ll u;ll v;scanf("%lld%lld",&u,&v);
		printf("%lld\n",cd(u,v));//大力计算就好了
	}return 0;//拜拜程序~
}

上一题