bzoj4539【HNOI2016】树

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

树分块+可持久化线段树,思路好题

时间: 2024-08-11 01:35:05

bzoj4539【HNOI2016】树的相关文章

[BZOJ4539][HNOI2016]树(主席树)

4539: [Hnoi2016]树 Time Limit: 40 Sec  Memory Limit: 256 MBSubmit: 746  Solved: 292[Submit][Status][Discuss] Description 小A想做一棵很大的树,但是他手上的材料有限,只好用点小技巧了.开始,小A只有一棵结点数为N的树,结点的编号为1,2,-,N,其中结点1为根:我们称这颗树为模板树.小A决定通过这棵模板树来构建一颗大树.构建过程如下:(1)将模板树复制为初始的大树.(2)以下(2

BZOJ4539: [Hnoi2016]树

复制的树缩点,主席树查k小,毫无技术含量,纯码农题. #include<bits/stdc++.h> #define u first #define v second #define F lower_bound #define I (i+j+2>>1) #define J (i+j>>1) using namespace std; int n1,n2,m,n4; typedef long long ll; map<ll,int>nu; const int N

【主席树启发式合并】【P3302】[SDOI2013]森林

Description 给定一个 \(n\) 个节点的森林,有 \(Q\) 次操作,每次要么将森林中某两点联通,保证操作后还是个森林,要么查询两点间权值第 \(k\) 小,保证两点联通.强制在线. Limitation \(1~\leq~n,~Q~\leq~80000\) Solution 考虑有连边还有查询链上第 \(k\) 大,于是要么用 LCT,要么用主席树. 考虑如果用 LCT 的话,并不能快速的维护两点间链的信息(其实感觉在access的时候乱搞一下有希望在多一个 \(\log\) 的

BZOJ 4541: [Hnoi2016]矿区 平面图转对偶图+DFS树

4541: [Hnoi2016]矿区 Time Limit: 30 Sec  Memory Limit: 512 MBSubmit: 433  Solved: 182[Submit][Status][Discuss] Description 平面上的矿区划分成了若干个开发区域.简单地说,你可以将矿区看成一张连通的平面图,平面图划分为了若 干平面块,每个平面块即为一个开发区域,平面块之间的边界必定由若干整点(坐标值为整数的点)和连接这些整点 的线段组成.每个开发区域的矿量与该开发区域的面积有关:具

【BZOJ4540】【Hnoi2016】序列 线段树

Claris劲啊!CA劲啊! %%%两位线段树做法传送门在这里和这里 逆向题解时间到: 首先将询问按照终点排序,并且一边从到遍历,不妨设当前遍历到了点,对于之前的每个点,我们维护两个值和.(之后的点的两个值都先设成0) 其中表示从这个点到之间序列A的最小值,而,表示从我们遍历第一个点到当前的所有时刻下的各个历史版本的和.(当遍历的点在这个点之前等于零)(事实上.) 不(很)难发现对于每一个询问,当且仅当时,有.因为 也就是说,如果我们把询问全部离线下来,遍历的时候可以快速更新并且求和,,我们就可

【bzoj4540】[Hnoi2016]序列 单调栈+离线+扫描线+树状数组区间修改

题目描述 给出一个序列,多次询问一个区间的所有子区间最小值之和. 输入 输入文件的第一行包含两个整数n和q,分别代表序列长度和询问数.接下来一行,包含n个整数,以空格隔开,第i个整数为ai,即序列第i个元素的值.接下来q行,每行包含两个整数l和r,代表一次询问. 输出 对于每次询问,输出一行,代表询问的答案. 样例输入 5 5 5 2 4 1 3 1 5 1 3 2 4 3 5 2 5 样例输出 28 17 11 11 17 题解 单调栈+离线+扫描线+树状数组区间修改 首先把使用单调栈找出每个

【bzoj】4538: [Hnoi2016]网络

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=4538 维护一个数据结构支持对于一颗树的操作,需要支持: 1.对于树上的一条路径上的每个点上放一个值. 2.撤销某次操作的路劲放. 3.查询除了经过这个点的路径的最大值. 往一个路径上丢值相当于往不经过条路径的所有点上丢值. 用一个树链剖分即可维护,对于操作区间取反. 直接查询单点最大值即可. 为了维护单点最大值,线段树中的每一个点对应两个堆,用于维护插入誉删除. 防止爆空间,所以标记永久

bzoj4541【HNOI2016】矿区

4541: [Hnoi2016]矿区 Time Limit: 30 Sec  Memory Limit: 512 MB Submit: 282  Solved: 123 [Submit][Status][Discuss] Description 平面上的矿区划分成了若干个开发区域.简单地说,你可以将矿区看成一张连通的平面图,平面图划分为了若 干平面块,每个平面块即为一个开发区域,平面块之间的边界必定由若干整点(坐标值为整数的点)和连接这些整点 的线段组成.每个开发区域的矿量与该开发区域的面积有关

HNOI2016 网络 (BZOJ4538)

HNOI2016 Day1 T2 网络 题意:给定一棵树,然后有若干个操作,可以新添加一条从u到v的权值为w的路径,或者将之前的一条路径删掉,动态询问不经过某个点的路径的最大权值 正解:树链剖分+线段树+堆 考场上面打了一个裸裸的树链剖分,连线段树都没套,复杂度是O(m^2 logn)的.当时真是傻了,只要套一个堆就可以AC了...我真傻,真的 首先考虑先树链剖分,然后看怎么处理这三个操作 显然题目要求我们动态维护不经过一个点的最大路径权值,那么我们就考虑用堆 每个线段树的结点里面存两个堆,都是