AcWing 5417. 树上搜索 第32次CCF-CSP计算机软件能力认证

1.模拟实现这一树上二分的方法,最终TLE60分

#include <bits/stdc++.h>
using namespace std;

const int N = 2010;
typedef long long ll;

typedef struct node{
    int num;//自己的编号
    ll w;//自己的权值
    ll w_all;//自己加上子树的权值
    ll w_cost;// 删除该节点后,父节点需要增加的权值

    bool is_live;// 节点是否存活的标志

    node* fa;//指向自己的父亲

    vector<node*> childs;//指向自己的孩子

    //初始置零
    node(){
        num = w = w_all = 0;
        fa = NULL;
    }
}node;

node v[N]; //存放节点

//对于当前节点的每一个子节点c,函数会递归地调用自身,计算子节点c的累积权值
// 最后,将子节点c的累积权值加到当前节点的累积权值w_all上
// 这样,当函数遍历完整棵树后
// 每个节点的w_all都会包含它自己的权值以及所有子节点的累积权值。
void travel(node* now)
{
        now -> w_all += now -> w;//首先将自己的权值w加到它的累积权值w_all上
        for(node* c : now -> childs)
        {
            travel(c);
            now->w_all += c->w_all;
        }
}

signed main()
{
    int n,m;//分别表示全部类别的数量和需要测试的类别的数量
    cin>>n>>m;
    for(int i = 1 ; i <= n ; i++)
    {
        v[i].num = i;
        cin>>v[i].w;
    }
    for(int i = 2 ; i <= n ; i ++)
    {
        int f;//上级编号
        cin>>f;
        v[i].fa = &v[f];//&v[f]表示获取向量v的第f个元素的地址(它爹的地址)
        v[f].childs.push_back(&v[i]);//指向儿子
    }

    node* root = &v[1];//其中编号为 1 的是根类别
    travel(root);

    while(m--)
    {
        int x;
        cin>>x;
        node* now = root;//一开始树是完整的
        for(int i = 1 ; i <= n ; i++)
        {
            v[i].w_cost=0;
            v[i].is_live=true;
        }
        //出口在哪?
        while(1)
        {
            //需要找到这树上最小的点
            node* ans = now;//先标记一个最大的root权值
            queue<node*> Q;
            Q.push(now);
            bool cnt = false;
            while(!Q.empty())
            {
                node* p = Q.front();
                Q.pop();
                /*
                    大模拟,模拟了一个树上二分的算法。
                    题目的公式是 其他的节点之和 - 自子子树的和
                    等价于 全部节点之和(根的子树之和) - 2倍自己子树之和
                    由于要删点,专门开一个cost去记录删去的值
                    真实的节点和是all-cost
                    综上:公式是abs((根的all-cost) - 2*(自己的all-cost))
                    这样就可以得到每一次的最小节点
                    然后去和每次给出的类别编号判断
                    然后选择去 保留 或者 删除
                */
                //计算一下
                ll A = abs(now->w_all-now->w_cost-2*(ans->w_all-ans->w_cost));
                ll B = abs(now->w_all-now->w_cost-2*(p->w_all-p->w_cost));if (A > B || (A==B&&p->num < ans->num))
                    ans = p;

                for (node* c : p->childs){
                    if (c->is_live){
                        cnt = true;
                        Q.push(c);
                    }
                }
            }
            if (!cnt) break;//树上没有东西了
            // 输出当前找到的最小节点
            cout << ans->num << " ";
            // 判断查询的节点是否在当前树上
            node* p = &v[x]; // 将查询节点指针指向对应节点
            bool flag = 0; // 标记查询节点是否在树上
            while (p != now->fa)
            {   // 遍历直到到达当前节点的父节点
                if (p == ans)
                {
                    flag = 1;
                    break;
                }
                // 如果查询节点是当前最小节点,标记为在树上
                p = p->fa; // 向上移动到父节点
            }
            if (flag) { // 如果查询节点在树上
                now = ans; // 保留当前最小节点
            } else {
                // 删除当前最小节点
                ans->is_live = false; // 标记为已删除
                node* p2 = ans->fa; // 获取当前最小节点的父节点
                // 更新父节点的权值
                while (p2 != now->fa) {
                    p2->w_cost += ans->w_all - ans->w_cost; // 更新父节点的删除权值
                    p2 = p2->fa; // 向上移动到父节点
                }
            }
        }
        cout << endl; // 输出换行符,表示当前查询结束
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值