4539: [Hnoi2016]树
Time Limit: 40 Sec Memory Limit: 256 MB
Submit: 415 Solved: 157
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; }
树分块+可持久化线段树,思路好题