【CCPC2021网络选拔赛(重赛)】HDU-7136 Jumping Monkey 题解

题目大意

给定一个有 n n n个节点的树,每个节点有一个权值 v a l [ i ] val[i] val[i],一个猴子在每个节点之间进行移动,假如它在节点 u u u上,如果存在节点 v v v v a l [ v ] val[v] val[v]是从 u u u v v v的路径上权值最大的点,那么它就可以从 u u u移动到 v v v上。问:猴子分别从 1 1 1 n n n号节点出发,分别最多能移动到多少的节点上?

思路

我们先观察一下,可以得出以下结论:

  1. 在一个节点 u u u上移动的最优策略是,移动到它能移动到的最小的节点 v v v上,用 a n s ans ans记录每个点的答案的话, a n s [ u ] = a n s [ v ] + 1 ans[u]=ans[v]+1 ans[u]=ans[v]+1。也就是说,此时,节点 u u u与哪些点相连已经不重要了,它的答案只与 v v v有关。

  2. 每个点点移动的终点都是整个树中权值最大的点,假设为 s s s,如果把 s s s删去,树变得不连通了,分成了几个小的连通块,就意味着不同连通块之间是无法相互抵达的,因为它们都需要经过权值最大的点 s s s

    如果我们在删的过程中,更新一下 a n s ans ans,删掉 s s s时,令 a n s [ s ] = 1 ans[s]=1 ans[s]=1

    之后再剩下的连通块中,也做同样的操作,假设其中一个连通块中权值最大的点为 x x x,删去后,令 a n s [ x ] = a n s [ s ] + 1 ans[x] = ans[s] + 1 ans[x]=ans[s]+1

    因为每个连通块中节点的终点也是此连通块中权值最大的点,而到了这个点之后,就只能去节点 s s s了。这么递归做下来, a n s [ i ] ans[i] ans[i]就是节点 i i i的答案了。

但是第二个结论很难直接去做。而第一个结论中移动到它能移动到的最小的节点 v v v,这个 v v v的寻找也很困难。但是我们可以借用2的思路,去寻找 v v v。新建一棵树,把 u u u v v v相连,形成一棵新的树,以权值最大的节点为根,那么每个节点的深度就是这个点的答案。

例子

假设原有的图是(数字代表权值): 3 - 1 - 2 - 5 - 4

新的树: 1 - 2 - 3 - 5 - 4。

具体做法

我们把2的做法逆过来,即从权值最小的点开始加入,依次加入权值递增的节点,形成不同的连通块。

每当我们加入一个节点 u u u时,扫描它在原有的图的出边,检测是否有已经加入的节点,如果有节点(这个节点所在的连通块中的所有点权值一定都是小于 u u u的),设为 v v v,说明 v v v所在的连通块可以以 u u u作为终点。所以,把 u u u v v v所在的连通块中,权值最大的点在新的图中连一条边。

我们利用并查集来记录连通块,因为我们是按照递增顺序遍历节点的,所以可以很容易做到每个连通块的并查集父亲为当前连通块中权值最大的点

代码

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;

const int maxN  =1e5 + 7;

int T, n, fa[maxN], depth[maxN];
vector<int> e1[maxN], e2[maxN];

struct Node {
    int val, num;
}a[maxN];

int Find(int x)
{
    return fa[x] == x? x : fa[x] = Find(fa[x]);
}

bool cmp(Node x, Node y)
{
    return x.val < y.val;
}

inline void init()
{
    for(int i = 1; i <= n; ++i) {
        fa[i] = depth[i] = 0;
        e1[i].clear();
        e2[i].clear();
    }
}

void dfs(int now)
{
    for(int i = 0; i < e2[now].size(); ++i) {
        int y = e2[now][i];
        if(depth[y])
            continue;
        depth[y] += depth[now] + 1;
        dfs(y);
    }
}

inline void work()
{
    scanf("%d", &n);
    init();
    for(int i = 1; i < n; ++i) {
        int x, y; scanf("%d%d", &x, &y);
        e1[x].push_back(y);
        e1[y].push_back(x);
    }
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &a[i].val);
        a[i].num = i;
    }
    sort(a + 1, a + 1 + n, cmp);
    for(int i = 1; i <= n; ++i) {
        fa[a[i].num] = a[i].num;
        for(int j = 0; j < e1[a[i].num].size(); ++j) {
            if(fa[e1[a[i].num][j]]) {
                int f = Find(e1[a[i].num][j]);
                fa[f] = a[i].num;
                e2[f].push_back(a[i].num);
                e2[a[i].num].push_back(f);
            }
        }
    }
    depth[a[n].num] = 1;
    dfs(a[n].num);
    for(int i = 1; i <= n; ++i)
        printf("%d\n", depth[i]);
}

int main()
{
    scanf("%d", &T);
    while(T--) {
        work();
    }
    return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值