2021 ccpc 赛后补题

jumping monkey

大致题意:

有一颗 n n n个节点的树,由 n − 1 n-1 n1条边连接而成,每个节点有一个权值 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值