题意:
就是给你一个树,然后给你m次询问,每次让你输出一个与a距离为b的的点,如果多个输出任意一个,如果没有那么输出-1。
思考:
- 看到这种距离某个点为多少距离的点,我就想到了四川省赛被100次bfs支配的恐惧:2021四川省赛。然而这两题并没有太多关系。对于树上点之间的距离一般也就是lca,但是这也不能把任意两个点都求出来呀,但是我又想了想找这个点最远的点?我可以求树的直径的两个端点,但是求出来最远的又有啥用呢?就没往下面想了,因为感觉这只是我的一些想法,不能代表是对的。
- 然后看了眼etitorial,发现就是求出来树的直径。这一个定心丸给了我信心。所以先把端点求出来,然后我就画了画图看每个点和这个树的直径的关系,我发现关系挺复杂的。因为我想默认以1作为根节点去建立lca,但是直径的两个端点有太多情况了。 然后我灵机一动,我完全可以把其中一个端点作为根节点建立lca呀。然后对于每个点,到他的距离的最短的点肯定是这两个端点的其中一个。
- 所以当是第一个端点maxn1的时候,由于我让maxn1当根节点,那么a点距离maxn1最大就是dep[a]-dep[maxn1],如果>=b的话,那么我就直接求距离a为b的点,树上倍增query就可以了,如果<b的话那么就是-1。
- 如果是第二个端点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的时候必须从深度大的往小的跳,所以找点的时候,可以把从上面往下找的转化成从下面往上找的。前提是要在一条链上。 - 到此这个题目就结束了,主要就是树的直径的应用。树的直径性质:任意一点到直径的端点的某个点的距离是最大的,两个端点一定是叶子节点。
代码:
#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;
}
总结:
多多思考,多画图看看,画图会让思维更明显,而且就算不对,也会一点一点推进,慢慢就把一些特殊情况考虑了。