实战演练 test16 T3

题目描述

      【题目背景】
      小 X 是远近闻名的学佛,平日里最喜欢做的事就是蒸发学水。
 
      小 X 所在的城市 X 城是一个含有 N 个节点的无向图,同时,由于 X 国是一个发展中国家,为了节约城市建设的经费,X 国首相在建造 X 城时只建造 N – 1条边,使得城市的各个地点能够相互到达。
      小 X 计划蒸发 Q 天的学水,每一天会有一名学水从 A 地走到 B 地,并在沿途各个地点留下一个水塘。此后,小 X 会从 C 地走到 B 地,并用佛光蒸发沿途的水塘。由于 X 城是一个学佛横行的城市,学水留下的水塘即使没有被小 X 蒸发,也会在第二天之前被其他学佛蒸发殆尽。
      现在,小 X 想要知道,他每一天能够蒸发多少水塘呢?

输入

      第一行三个整数 N、Q、NUM,分别表示 X 城地点的个数,小 X 蒸发学水的天数,以及测试点编号。注意,测试点编号是为了让选手们更方便的获得部分分,你可能不需要用到这则信息,在下发的样例中,测试点编号的含义是该样例满足某一测试点限制。
      接下来 N – 1 行,每行两个整数 X、Y,表示 X 地与 Y 地之间有一条边。
      接下来 Q 行,每行三个整数 A、B、C,表示一天中,有一名学水从 A 地走到B 地,而小 X 会从 C 地走到 B 地。


输出

      输出 Q 行,每行一个整数,表示小 X 能够蒸发的水塘数。

样例输入

3 3 1
1 2
2 3
1 2 3
1 1 3
3 1 3

样例输出

1
1
3

提示


      更多样例:下载

         

自己的想法:

性质1  树退化成一条链 还是很好骗分的呀

性质2  dfs一遍 记录步数即水塘数


题解:

【测试点 1-10】  对于每次询问,将两条路径上的点全部加上 1,然后统计 2 的数量,作为答案输出,询问与询问之间注意清空数组。  该算法的时间复杂度是 O(N * Q)的。 

【*测试点 1-16】  对于某些数据结构学傻的同学,我提供了这一档部分分。事实上,我们可以用树链剖分的方法加速上述过程,注意清空数组时应该当 作为修改改回去,而不能直接 Memset,否则复杂度将退化。  该算法的时间复杂度是 O(N + Q * Log2 N)的。 

【测试点 11-13、17-18】  这些测试点满足特殊性质 1,树退化成一条链。  那么,我们只需要判断两个闭区间交的长度即可。  该算法的时间复杂度是 O(N + Q)的。 

【测试点 11、14-15、17、19】  这些测试点满足特殊性质 2,A = C。  问题简化成求 A 与 B 之间的距离,显然可以用树上倍增或 Tarjan 算法求出 A 与 B 的 LCA,那么 A 与 B 之间的距离就等于 A 的深度加 B 的深度减去 LCA 的 深度的两倍。  该算法的时间复杂度是 O(N + Q)或 O(( N + Q)* Log N)的。 

【测试点 1-20】  注意到问题实际上是问树上一个点到另外两点路径的重合长度,也就是说, 这两条路径有一个端点是重合的,不妨就此性质构建算法。树上三点,或是呈一条链的形式,或是存在一个中心,使得中心到三点之间 的路径不相交。如下图,上方的两种情况是 A、B、C 点存在中心 O,下方两种情况是 A、B、C 点在一条链上。

 
 
 对于呈一条链的形式,我们不妨将三个点中,位于中间的那个点称为三个点 的中心,如此一来,我们要求的答案实际上是中心到 B 点的距离。  那么,如何求中心呢?通过观察上图,我们发现,中心一定是 A、B、C 点中 两点的 LCA。并且,通过进一步观察,我们可以发现,中心是 A、B、C 点两两的 LCA 中,深度最大的点。  所以,我们只需求出中心,在求出中心到 B 点的距离即可解决本题。  该算法的时间复杂度是 O(N + Q)或 O(( N + Q)* Log N)的。 


代码实现:

#include<bits/stdc++.h>
#define MAXN	200005
#define MAXLOG	20
using namespace std;
template <typename T> void read(T &x){
	x=0;int f=1;char ch=getchar();
	for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
	for(;isdigit(ch);ch=getchar())x=(x<<1)+(x<<3)+ch-'0';
	x*=f;
}
int n,q,num;
vector<int> a[MAXN];
int fa[MAXN][MAXLOG],depth[MAXN];

void work(int pos,int f){
	fa[pos][0]=f;
	depth[pos]=depth[f]+1;
	for(int i=1;i<MAXLOG;++i)
		fa[pos][i]=fa[fa[pos][i-1]][i-1];
	for(int i=0;i<a[pos].size();++i)
		if(a[pos][i]!=f) work(a[pos][i],pos); 
}

int lca(int x,int y){
	if(depth[x]<depth[y]) swap(x,y);
	for(int i=MAXLOG-1;i>=0;--i)
		if(depth[fa[x][i]]>=depth[y]) x=fa[x][i];
	if(x==y) return x;
	for(int i=MAXLOG-1;i>=0;--i)
		if(fa[x][i]!=fa[y][i]){
			x=fa[x][i];
			y=fa[y][i];
		}
	return fa[x][0];
}

int dist(int x,int y){
	int z=lca(x,y);
	return depth[x]+depth[y]-2*depth[z];
}

int main(){
	read(n),read(q),read(num);
	for(int i=1;i<n;++i){
		int x,y;
		read(x),read(y);
		a[x].push_back(y);
		a[y].push_back(x);
	}
	work(1,0);
	for(int i=1;i<=q;++i){
		int A,B,C;
		read(A),read(B),read(C);
		int nowl=lca(A,B);
		int nowll=lca(B,C);
		int nowlll=lca(A,C);
		if(nowl<nowll) nowl=nowll;
		if(nowl<nowlll) nowl=nowlll;
		cout<<dist(B,nowl)+1<<endl;
	}
 	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值