4539: [Hnoi2016]树
Time Limit: 40 Sec
Memory Limit: 256 MB
Submit: 415
Solved: 157
[ 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之间的距离是多少。
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条边。
树分块+可持久化线段树,思路好题
新树的节点较多,直接表示比较麻烦,我们考虑简化树的形态。
将每次操作的一棵子树看成一块,每一块用根节点表示,可以得到一棵新树,即新树中一个节点代表一块。
对于一次询问,如果两个点在同一块内,则直接在原树中求LCA计算答案。如果不在同一块中,要分两块在新树中是否是父子关系两种情况,然后就是比较细节的问题了。
还有一个问题,新树中的节点编号如何对应原树中的节点编号。首先二分出这个节点属于第几块,然后转化成求一个子树中编号第k大的点,对于原树的DFS序建可持久化线段树。
实现起来很麻烦,各种各样的情况,足足调了一下午。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#define F(i,j,n) for(int i=j;i<=n;i++)
#define D(i,j,n) for(int i=j;i>=n;i--)
#define ll long long
#define N 100005
#define M 2000000
using namespace std;
int n,m,q,tim,now;
int dfn[N],last[N],root[N],from[N];
ll num[N];
struct edge{int next,to;ll v;};
struct Segment
{
int cnt,sz[M],ch[M][2],rt[N];
void insert(int x,int &y,int l,int r,int pos)
{
y=++cnt;sz[y]=sz[x]+1;
if (l==r) return;
int mid=(l+r)>>1;
if (pos<=mid) ch[y][1]=ch[x][1],insert(ch[x][0],ch[y][0],l,mid,pos);
else ch[y][0]=ch[x][0],insert(ch[x][1],ch[y][1],mid+1,r,pos);
}
void insert(int x,int v){insert(rt[x-1],rt[x],1,n,v);}
int query(int x,int y,int l,int r,int k)
{
if (l==r) return l;
int mid=(l+r)>>1,tmp=sz[ch[y][0]]-sz[ch[x][0]];
if (tmp>=k) return query(ch[x][0],ch[y][0],l,mid,k);
else return query(ch[x][1],ch[y][1],mid+1,r,k-tmp);
}
int query(int x,int y,int k){return query(rt[x-1],rt[y],1,n,k);}
}T;
struct Graph
{
edge e[N*2];
int cnt,head[N],fa[N][20],dep[N],sz[N];
ll dis[N];
void add_edge(int x,int y,ll v)
{
e[++cnt]=(edge){head[x],y,v};head[x]=cnt;
e[++cnt]=(edge){head[y],x,v};head[y]=cnt;
}
void dfs(int x)
{
F(i,1,18) fa[x][i]=fa[fa[x][i-1]][i-1];
sz[x]=1;
for(int i=head[x];i;i=e[i].next)
{
int y=e[i].to;
if (y!=fa[x][0])
{
fa[y][0]=x;
dis[y]=dis[x]+e[i].v;
dep[y]=dep[x]+1;
dfs(y);
sz[x]+=sz[y];
}
}
}
void dfs2(int x)
{
dfn[x]=++tim;T.insert(tim,x);
for(int i=head[x];i;i=e[i].next)
{
int y=e[i].to;
if (y!=fa[x][0]) dfs2(y);
}
last[x]=tim;
}
int lca(int x,int y)
{
if (dep[x]<dep[y]) swap(x,y);
int tmp=dep[x]-dep[y];
D(i,18,0) if ((1<<i)&tmp) x=fa[x][i];
if (x==y) return x;
D(i,18,0) 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]-dis[lca(x,y)]*2;}
int up(int x,int y)
{
int tmp=dep[x]-dep[y]-1;
D(i,18,0) if ((1<<i)&tmp) x=fa[x][i];
return x;
}
}ori,g;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
inline int getid(ll x,int len){return lower_bound(num+1,num+len+1,x)-num;}
ll query(ll a,ll b)
{
int ida=getid(a,m+1),rta=root[ida],posa=T.query(dfn[rta],last[rta],a-num[ida-1]);
int idb=getid(b,m+1),rtb=root[idb],posb=T.query(dfn[rtb],last[rtb],b-num[idb-1]);
if (ida==idb) return ori.dist(posa,posb);
int lca=g.lca(ida,idb);
if (g.dep[ida]>g.dep[idb]) swap(ida,idb),swap(rta,rtb),swap(posa,posb);
if (ida==lca)
{
int frb=from[g.up(idb,lca)];
return g.dist(ida,idb)-(ori.dis[frb]-ori.dis[rta])+ori.dist(frb,posa)+ori.dis[posb]-ori.dis[rtb];
}
else
{
int fra=from[g.up(ida,lca)],frb=from[g.up(idb,lca)];
return g.dist(ida,idb)-(ori.dis[fra]+ori.dis[frb]-ori.dist(fra,frb)-ori.dis[root[lca]]*2)+ori.dis[posa]-ori.dis[rta]+ori.dis[posb]-ori.dis[rtb];
}
}
int main()
{
n=read();m=read();q=read();
F(i,1,n-1){int x=read(),y=read();ori.add_edge(x,y,1);}
ori.dfs(1);ori.dfs2(1);
num[1]=n;root[1]=1;now=1;
F(i,2,m+1)
{
ll x,y;scanf("%lld%lld",&x,&y);
int id=getid(y,i-1),rt=root[id];
root[i]=x;now=i;
num[i]=num[i-1]+ori.sz[x];
from[i]=T.query(dfn[rt],last[rt],y-num[id-1]);
g.add_edge(id,i,ori.dis[from[i]]-ori.dis[rt]+1);
}
g.dfs(1);
F(i,1,q)
{
ll x,y;scanf("%lld%lld",&x,&y);
printf("%lld\n",query(x,y));
}
return 0;
}
树分块+可持久化线段树,思路好题