jumping monkey
大致题意:
有一颗 n n n个节点的树,由 n − 1 n-1 n−1条边连接而成,每个节点有一个权值 a i a_i ai,对于树上任意两个节点 u u u, v v v,monkey能从 u u u跳到 v v v当且仅当 a v a_v av是从 u u u到 v v v的最短路径(由于是树,故任意两个节点只有一条不经过重边的路,最短路径即该条路)所经过节点中权值最大的节点,问monkey从 k ( k ∈ ( 1 , n ) ) k(k\in(1,n)) k(k∈(1,n))出发,最多能经过多少个节点。
分析:刚开始的想法是求出每个节点
i
i
i能跳的所有节点中最小的那一个——记为
b
[
i
]
b[i]
b[i],然后对于节点
i
i
i,下一步跳到
b
[
i
]
b[i]
b[i]即可(对
b
[
i
]
b[i]
b[i],跳到
b
[
b
[
i
]
]
b[b[i]]
b[b[i]],以此类推),当跳到最后一个节点时结束,经过的节点数为
a
n
s
ans
ans(实际最后解法的本质也是如此),但是实现求最近节点的方式用了复杂度较高的算法,导致tl。
较好的实现方式为建立一棵树,
i
i
i的父节点为
b
[
i
]
b[i]
b[i],这样每个节点的深度即为ans(根节点深度为
1
1
1),我们通过按权值从小到大枚举节点,对于节点
i
i
i,枚举他能到达的节点中比他小的(枚举过的),找到该节点的根节点(后枚举到的节点i的权值一定大于原先的根节点),将i接上去即可。
可这样实现复杂度仍然较高(问题出在找根节点上,当树深度较大时一直向上找根节点递归次数多),这时想到使用并查集的方式,对于节点
i
i
i,设置两个属性
r
o
o
t
[
i
]
root[i]
root[i]和
f
a
[
i
]
fa[i]
fa[i],分别指树的根节点和该节点的直接父节点,当找根节点时,更新沿途中所有节点的根节点,这样下次递归找根节点时复杂度大大降低,而求
a
n
s
ans
ans时可以用
a
n
s
[
i
]
=
a
n
s
[
f
a
[
i
]
]
+
1
ans[i]=ans[fa[i]]+1
ans[i]=ans[fa[i]]+1。最后注意求ans时要按权值从大到小求,因为小节点的ans是由大节点递推出来的。
ac代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 1e5 + 5;
vector<int> edge[N];
int a[N];
int order[N];//order数组记录权值从小到大的顺序
int fa[N];
int root[N];
int ans[N];
bool cmp(int x, int y)
{
return a[x] < a[y];
}
int getroot(int x)
{
if (root[x] == x)
{
return x;
}
int r = getroot(root[x]);
ans[x] = ans[fa[x]] + 1;
root[x] = r;
//cout << "ans" << x << "=" << ans[x] << endl;
return r;
}
#if 0
int solve(int i)
{
if (fa[i] == i)
{
ans[i] = 1;
return ans[i];
}
if (ans[i])
return ans[i];
ans[fa[i]] = solve(fa[i]);
ans[i] = ans[fa[i]] + 1;
return ans[i];
}
#endif
int main()
{
ios::sync_with_stdio(false), cin.tie(0);
int t;
cin >> t;
int n;
while (t--)
{
cin >> n;
for (int i = 1; i <= n; i++)
{
edge[i].clear();
order[i] = fa[i] = root[i] = i;
ans[i] = 0;
}
int u, v;
for (int i = 1; i <= n - 1; i++)
{
cin >> u >> v;
edge[u].push_back(v);
edge[v].push_back(u);
}
for (int i = 1; i <= n; i++)
cin >> a[i];
sort(order + 1, order + n + 1, cmp);
for (int i = 1; i <= n; i++)
{
int now = order[i];
for (auto v : edge[now])
{
if (a[v] < a[now])
{
int r = getroot(v);
fa[r] = now;
root[r] = now;
ans[r]++;
}
}
}
for (int i = n; i >= 1; i--)//按权值从大到小递推
getroot(order[i]);
for (int i = 1; i <= n; i++)
cout << ans[i] + 1 << '\n';
}
return 0;
}