CSP-树上搜索 满分题解

题目回顾

树上搜索icon-default.png?t=N7T8https://sim.csp.thusaac.com/contest/32/problem/2

C++满分题解

思路概述

        接下来设计数据结构。存在对树的很多插入,剪枝操作,毋庸置疑用链表实现最为合适。 由于完成一次类别测试后,需要树结构的恢复,所以增加parent结点来快速完成剪枝后的拼接而不用从输入重塑树结构。

typedef   struct  treeNode
{
    int index;
    long long weight;
    treeNode* child;
    treeNode* lastChild;
    treeNode* brother;
    treeNode* prevBrother;
    treeNode* parent;
    int childID;

    treeNode(long long weight, int index) {
        this->weight = weight;
        this->index = index;
        this->parent = nullptr;
        this->child = nullptr;
        this->lastChild = nullptr;
        this->brother = nullptr;
        this->prevBrother = nullptr;
    }
} tree;
treeNode* myTree[2010];

        下面我们来实现程序框图里的抽象过程 。

根据输入构建树结构

        首先通过第一、二行输入,初始化所有的结点的 index 和 weight,通过第三行输入来实现结点之间的连接,构建出一棵树。我们定义一个函数来实现两结点之间的链接,顺序遍历第三行输入就能把一整棵树连接出来。

void linkNodes(treeNode* parent, treeNode* child) {
    child->parent = parent;
    if (!parent->child) {
        // 如果parent结点还没有孩子
        parent->child = child;
        parent->lastChild = child;
    }
    else
    {
        // 如果parent结点已经有了孩子
        parent->lastChild->brother = child;
        child->prevBrother = parent->lastChild;
        parent->lastChild = child;
    }
}

计算当前结点各子孙结点的\omega_{ \theta}

        记任意结点i的根子树的权重和为Ω,根节点为结点0,则有:

\omega _{​{\theta}i} = \left | \Omega _{i}-(\Omega_{0}-\Omega_{i}) \right |=\left | 2\Omega _{i}-\Omega_{0} \right |

        所以计算\omega_{ \theta}可以简化为计算各个结点的根子树权重和。定义一个函数并重载两种输入:

计算node结点的根子树权重和:

long long calculateWeight(treeNode* node) {   //计算node的根子树权重和
    long long weight1 = node->weight;
    node = node->child;
    while (node)
    {
        weight1 += calculateWeight(node);
        node = node->brother;
    }
    return weight1;
}

以node为根,遍历其子树,计算 \omega_{ \theta},并找到最小的\omega_{ \theta}

void calculateWeight(treeNode* node, long long &currentMin, int &index, long long currentSum) {
    treeNode* temp;
    long long weight_theata = abs(currentSum - 2 * calculateWeight(node));
    if (weight_theata < currentMin || (weight_theata == currentMin && node->index < index)) {
        currentMin = weight_theata;
        index = node->index;
    }
    
    temp = node->child;
    while (temp) {
        calculateWeight(temp, currentMin, index, currentSum);
        temp = temp->brother;
    }
}

判断测试类别是否在选取结点的根子树上

        简单来说就是判断选择的这个结点(\omega_{ \theta}最小的)是不是测试节点的祖先结点。

bool judge(int selectID, int testID) {
    bool flag = false;
    if (selectID == testID)
        return true;

    treeNode* currentNode = myTree[selectID]->child;

    while (currentNode && !flag) {
        flag = judge(currentNode->index, testID);
        currentNode = currentNode->brother;
    }

    return flag;
}

 减除所选结点为根的树

void removeBrunch(treeNode* node) {
    treeNode* parent = node->parent;

    if (!node->brother) {
        // 如果他没有右兄弟
        parent->lastChild = node->prevBrother;
    }
    else {
        node->brother->prevBrother = node->prevBrother;
    }
    if (!node->prevBrother) {
        //如果他没有左兄弟
        parent->child = node->brother;
    }
    else {
        node->prevBrother->brother = node->brother;
    }

    node->brother = nullptr;
    node->prevBrother = nullptr;
    node->parent = nullptr;
    
    removedNode.push_back(node);
    // 保存用以恢复树结构
    removedNodesParent.push_back(parent);
}

重建回最原始的树

void rebuild() {

    for (int i = 0; i < removedNode.size(); i++) {
        linkNodes(removedNodesParent[i], removedNode[i]);
    }
    
    removedNodesParent.clear();
    removedNode.clear();
}

完整代码:

# include <iostream>
# include<vector>
# include<cmath>
#include <algorithm>
using namespace std;

int n, m;

inline long long read() {
    long long x = 0;
    int signe = 1;
    char c = getchar();
    while (c < '0' || c>'9') {
        if (c == '-')
            signe = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') {
        x = x * 10 + c - '0';
        c = getchar();
    }
    return x * signe;
}

typedef   struct  treeNode
{
    int index;
    long long weight;
    treeNode* child;
    treeNode* lastChild;
    treeNode* brother;
    treeNode* prevBrother;
    treeNode* parent;
    int childID;

    treeNode(long long weight, int index) {
        this->weight = weight;
        this->index = index;
        this->parent = nullptr;
        this->child = nullptr;
        this->lastChild = nullptr;
        this->brother = nullptr;
        this->prevBrother = nullptr;
    }
} tree;
treeNode* myTree[2010];

void linkNodes(treeNode* parent, treeNode* child) {
    child->parent = parent;
    if (!parent->child) {
        // 如果parent结点还没有孩子
        parent->child = child;
        parent->lastChild = child;
    }
    else
    {
        // 如果parent结点已经有了孩子
        parent->lastChild->brother = child;
        child->prevBrother = parent->lastChild;
        parent->lastChild = child;
    }
}

long long calculateWeight(treeNode* node) {
    long long weight1 = node->weight;
    node = node->child;
    while (node)
    {
        weight1 += calculateWeight(node);
        node = node->brother;
    }
    return weight1;
}

void calculateWeight(treeNode* node, long long &currentMin, int &index, long long currentSum) {
    treeNode* temp;
    long long weight_theata = abs(currentSum - 2 * calculateWeight(node));
    if (weight_theata < currentMin || (weight_theata == currentMin && node->index < index)) {
        currentMin = weight_theata;
        index = node->index;
    }
    
    temp = node->child;
    while (temp) {
        calculateWeight(temp, currentMin, index, currentSum);
        temp = temp->brother;
    }
}

bool judge(int selectID, int testID) {
    bool flag = false;
    if (selectID == testID)
        return true;

    treeNode* currentNode = myTree[selectID]->child;

    while (currentNode && !flag) {
        flag = judge(currentNode->index, testID);
        if (flag)
            break;
        currentNode = currentNode->brother;
    }

    return flag;
}

vector<treeNode*> removedNode;
vector<treeNode*> removedNodesParent;
void removeBrunch(treeNode* node) {
    treeNode* parent = node->parent;

    if (!node->brother) {
        // 如果他没有右兄弟
        parent->lastChild = node->prevBrother;
    }
    else {
        node->brother->prevBrother = node->prevBrother;
    }
    if (!node->prevBrother) {
        //如果他没有左兄弟
        parent->child = node->brother;
    }
    else {
        node->prevBrother->brother = node->brother;
    }

    node->brother = nullptr;
    node->prevBrother = nullptr;
    node->parent = nullptr;
    
    removedNode.push_back(node);
    removedNodesParent.push_back(parent);
}

void rebuild() {

    for (int i = 0; i < removedNode.size(); i++) {
        linkNodes(removedNodesParent[i], removedNode[i]);
    }
    
    removedNodesParent.clear();
    removedNode.clear();
}

int main() {
     n = read(), m = read();

    // 初始化结点及其权重+编号
    for (int i = 1; i <= n; i++)
        myTree[i] = new treeNode(read(), i);

    //链接结点
    for (int i = 2; i <= n; i++)
        linkNodes(myTree[read()], myTree[i]);

    //开始测试
    for (int i = 1; i <= m; i++) {
        int testID = read();
        treeNode* currentNode = myTree[1];

        //结束条件:只剩余一个结点
        while (currentNode->child) {
            
            //计算各个结点的wθ,并找到最小的
            long long sum = calculateWeight(currentNode);
            long long min = sum;
            int minID = 0;
            calculateWeight(currentNode, min, minID, sum);
            
            cout << minID << " ";
            treeNode* selectesNode = myTree[minID];
        
            //判断当前选择结点与测试结点的从属关系
            if (judge(minID, testID)) {
                //如果判断为yes,只保留当前树枝
                currentNode = myTree[minID];
            }
            else//如果判断为no,减除当前树枝
            {
                removeBrunch(selectesNode);
            }
        }
        // 恢复最初的树结构
        rebuild();
        cout << '\n';
    }
}

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值