ABC-Exactly K Steps-(树的直径+lca)

F

题意:
就是给你一个树,然后给你m次询问,每次让你输出一个与a距离为b的的点,如果多个输出任意一个,如果没有那么输出-1。

思考:

  1. 看到这种距离某个点为多少距离的点,我就想到了四川省赛被100次bfs支配的恐惧:2021四川省赛。然而这两题并没有太多关系。对于树上点之间的距离一般也就是lca,但是这也不能把任意两个点都求出来呀,但是我又想了想找这个点最远的点?我可以求树的直径的两个端点,但是求出来最远的又有啥用呢?就没往下面想了,因为感觉这只是我的一些想法,不能代表是对的。
  2. 然后看了眼etitorial,发现就是求出来树的直径。这一个定心丸给了我信心。所以先把端点求出来,然后我就画了画图看每个点和这个树的直径的关系,我发现关系挺复杂的。因为我想默认以1作为根节点去建立lca,但是直径的两个端点有太多情况了。 然后我灵机一动,我完全可以把其中一个端点作为根节点建立lca呀。然后对于每个点,到他的距离的最短的点肯定是这两个端点的其中一个。
  3. 所以当是第一个端点maxn1的时候,由于我让maxn1当根节点,那么a点距离maxn1最大就是dep[a]-dep[maxn1],如果>=b的话,那么我就直接求距离a为b的点,树上倍增query就可以了,如果<b的话那么就是-1。
  4. 如果是第二个端点maxn2的时候,那么a点绝对不可能是maxn2的儿子。因为树的直径的两个端点必然是叶子节点,否则他可以往叶子点走,这样更优。所以那么可以求出来a和maxn2的lca。先看看dis(a,maxn2)是否>=b,如果>=b的话,先看看a到lca的距离是否>=b,如果够就直接求query(a,b)。否则我还需要的距离sum = b-dis(a,lca)。那么就找query(maxn2,dis(maxn2,lca)-sum)。
    因为query的时候必须从深度大的往小的跳,所以找点的时候,可以把从上面往下找的转化成从下面往上找的。前提是要在一条链上。
  5. 到此这个题目就结束了,主要就是树的直径的应用。树的直径性质:任意一点到直径的端点的某个点的距离是最大的,两个端点一定是叶子节点。

代码:

#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define db double
#define int long long
#define PII pair<int,int >
#define mem(a,b) memset(a,b,sizeof(a))
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);

using namespace std;
const int mod = 1e9+7,inf = 1e18;
const int N = 2e5+10,M = 2010;

int T,n,m,k;
int va[N];
int dis[N],dis1[N],dis2[N],maxn1,maxn2;
int acc[N][25],dep[N],cnt = 22;

vector<int > e[N];

void get(int now,int p,int dist[],int op)
{
	if(op==1&&dist[maxn1]<dist[now]) maxn1 = now;
	if(op==2&&dist[maxn2]<dist[now]) maxn2 = now;
	for(auto spot:e[now])
	{
		if(spot==p) continue;
		dist[spot] = dist[now]+1;
		get(spot,now,dist,op);
	}
}

void dfs(int now,int p)
{
	acc[now][0] = p;
	dep[now] = dep[p]+1;
	for(auto spot:e[now])
	{
		if(spot==p) continue;
		dfs(spot,now);
	}
}

int lca(int a,int b)
{
	if(dep[a]<dep[b]) swap(a,b);
	for(int i=cnt;i>=0;i--)
	{
		if(dep[acc[a][i]]>=dep[b])
		a = acc[a][i];
	}
	if(a==b) return a;
	for(int i=cnt;i>=0;i--)
	{
		if(acc[a][i]!=acc[b][i])
		{
			a = acc[a][i];
			b = acc[b][i];
		}
	}
	return acc[a][0];
}

int query(int a,int b)
{
	for(int i=cnt;i>=0;i--)
	{
		if(b>=(1ll<<i))
		{
			a = acc[a][i];
			b -= (1ll<<i);
		}
	}
	return a;
}

int solve1(int a,int b)
{
	int sum = dep[a]-dep[maxn1];
	if(sum<b) return -1;
	return query(a,b);
}

int solve2(int a,int b)
{
	int now = lca(maxn2,a);
	int sum = dep[a]-dep[now];
	int ans = dep[a]+dep[maxn2]-2*dep[now];
	if(ans<b) return -1;
	if(sum>=b) return query(a,b);
	else return query(maxn2,dep[maxn2]-dep[now]-(b-sum));
}

signed main()
{  
	IOS;
	cin>>n;
	for(int i=1;i<n;i++)
	{
		int a,b;
		cin>>a>>b;
		e[a].pb(b);
		e[b].pb(a);
	}
	get(1,0,dis,1);
	get(maxn1,0,dis1,2);
	get(maxn2,0,dis2,3);
	dfs(maxn1,0);
	for(int i=1;i<=cnt;i++)
	{
		for(int j=1;j<=n;j++)
		acc[j][i] = acc[acc[j][i-1]][i-1];
	}
	cin>>m;
	while(m--)
	{
		int a,b;
		cin>>a>>b;
		if(dis1[a]>=dis2[a]) cout<<solve1(a,b)<<"\n";
		else cout<<solve2(a,b)<<"\n";
	}
	return 0;
}

总结:
多多思考,多画图看看,画图会让思维更明显,而且就算不对,也会一点一点推进,慢慢就把一些特殊情况考虑了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值