【CSP】2022–12-4 聚集方差 暴力混分 65分思路 没有算法纯模拟

2022–12-4 聚集方差 暴力混分 65分思路 没有算法纯模拟

这题原本是需要优化的算法,但是鉴于我弱鸡的实力,考试的时候肯定是不会A出来的第四题的100分的,最多混一混(要是能混到也算是很不错了)。所以这题我就尝试使用暴力来解题,没想到还是拿到了超过了一半的分数。

但是从这道题我也明白了就算是混分也是需要训练的。

题目重述

这题别看那么多公式什么的,实际上就是几句话就能解释清楚。

首先背景是一个公司的,可以用一棵树来表示,上级就是父节点,下属就是子节点,然后对于每个节点我们可以获取一个可重复集合,就是包括自己所有的子节点(包括子节点的子节点,儿子的儿子……)就是整个子树的节点,和自己的集合。

之后我们明白这个集合,然后我们可以针对每一个集合计算一个ans 然后输出。这个ans计算方法就是,遍历这个集合中的所有元素找到离他最近的元素(不能是他自己)然后求出距离后平方,最后ans再加上这个值。

思路

主要就是分两部分,第一部分是求得每个节点的集合,第二部分就是求个每个集合的ans。

第一部分,我们先可以把每个节点的第一层子节点保存下来,然后再递归把每个节点的子树的所有的节点加进去

第二部分,我们先对每个集合进行排个序,然后遍历里面的每个元素,找前后两个点看谁最近,然后加到ans中,最后输出(有个特殊情况,就是只有一个节点的集合求出是0)

遇到的问题(学到的知识)

  1. 思路问题

本来想着直接用一个递归函数求得,就是边求集合边求每个元素的离得最近的距离,但是发现不太可行,因为集合不仅是每个节点的子节点,而且是整个子树(包含儿子的儿子)

这是幡然醒悟,然后开始写。

  1. 实现问题:找子节点无限递归无限循环

原来是因为我把每个节点的字节点存到了集合中,也把那个节点本身也存到的集合中,这样就会无限递归不能停止,而且会导致重复添加的问题。

修改办法就是:区别对待添加个if

void getChild(int i, int j)           // 父节点是i 子节点是j  要把j的所有子节点都填入到i的child数组中
{
    if (child1[j].size() == 1)
    {
        child[i].push_back(child1[j][0]);
        return;
    }
    for (auto item : child1[j])
    {
        if (item.id == j) // 因为此数组中包括本节点,防止无限递归所以使用了特殊处理此节点
        {
            child[i].push_back(item);
        }
        else
            getChild(i, item.id);
    }
}
  1. 还是发现总是有重复添加

原来在初始的第一层循环中,也是出现了上面的问题,也就是我遍历的两边子树,添加了两遍子树

修改前

    for (int i = 1; i <= n; i++)
    {
        for (auto item : child1[i])
        {
            getChild(i, item.id);
        }
    }

修改后

    for (int i = 1; i <= n; i++)
    {
        for (auto item : child1[i])
        {
            if (item.id == i)
            {
                child[i].push_back(item);
            }
            else
                getChild(i, item.id);
        }
    }

至此求集合的过程就结束了。然后是求ans的过程

  1. 求ans的过程其实比较简单,但是有一个非常坑的地方,以前都没有注意到。

首先可以发现是范围问题(虽然这个我也找了好久好久…………)然后将anx这个变量改为了long long 类型,但是发现还是0分

在这里插入图片描述

然后听到同学的建议,将所有int改为了long long 就变成40分

在这里插入图片描述

为什么会这样呢?

    for (int i = 1; i <= n; i++)
    {
        long long anx = 0; // 求和
        for (int j = 0; j < child[i].size(); j++)
        {
            long long mini = 1e9 + 1;
            if (j != 0)
            {
                mini = min(abs(child[i][j].a - child[i][j - 1].a), mini);
            }
            if ((j + 1) != child[i].size())
            {
                mini = min(abs(child[i][j].a - child[i][j + 1].a), mini);
            }
            if (mini != 1e9 + 1) // 有可能没有其他子节点了,所以就是加0 而不是(1e9)的平方
                anx += mini * mini;
        }
        cout << anx << endl;
    }

首先是mini这个变量要设置为long long 为什么呢?

因为有mini * mini 这个 1 0 9 × 1 0 9 10^9 \times 10^9 109×109 的话会超出int 的范围,就算最后赋给了long long 的anx,但是那是在赋值阶段才升级为longlong的,也就是相乘的时候还是int,结果也会是int,这就会导致溢出

然后我们只修改了mini为long long那么会报错 ,因为min()这个函数没办法比较 long long 和 int 所以我们得将所有的改为long long

至此再改下数组大小就达到了65分

在这里插入图片描述

其实还可以基于此进行优化,然后拿更高分数,有舍才有得,就先到这里了。

完整代码

#include <bits/stdc++.h>
using namespace std;
int n;
struct worker
{
    int id;      // 自己的id
    long long a; // 请假时间
    int p;       // 上级
} w[300001];

vector<struct worker> child[300001];  // 保存所有子节点(包括子节点的子节点)
vector<struct worker> child1[300001]; // 保存第一层子节点
void getChild(int i, int j)           // 父节点是i 子节点是j  要把j的所有子节点都填入到i的child数组中
{
    if (child1[j].size() == 1)
    {
        child[i].push_back(child1[j][0]);
        return;
    }
    for (auto item : child1[j])
    {
        if (item.id == j) // 因为此数组中包括本节点,防止无限递归所以使用了特殊处理此节点
        {
            child[i].push_back(item);
        }
        else
            getChild(i, item.id);
    }
}
bool cmp(struct worker a, struct worker b)
{
    return a.a < b.a;
}
int main()
{
    cin >> n;
    for (int i = 2; i <= n; i++) // 编号为1的是公司的唯一老板
    {
        cin >> w[i].p;
    }
    for (int i = 1; i <= n; i++)
    {
        cin >> w[i].a;
        w[i].id = i;
        child1[i].push_back(w[i]);      // 自己是自己的员工
        child1[w[i].p].push_back(w[i]); // 自己是上级的员工
    }
    for (int i = 1; i <= n; i++)
    {
        for (auto item : child1[i])
        {
            if (item.id == i)
            {
                child[i].push_back(item);
            }
            else
                getChild(i, item.id);
        }
    }
    for (int i = 1; i <= n; i++)
    {
        sort(child[i].begin(), child[i].end(), cmp);
    }

    for (int i = 1; i <= n; i++)
    {
        long long anx = 0; // 求和
        for (int j = 0; j < child[i].size(); j++)
        {
            long long mini = 1e9 + 1;
            if (j != 0)
            {
                mini = min(abs(child[i][j].a - child[i][j - 1].a), mini);
            }
            if ((j + 1) != child[i].size())
            {
                mini = min(abs(child[i][j].a - child[i][j + 1].a), mini);
            }
            if (mini != 1e9 + 1) // 有可能没有其他子节点了,所以就是加0 而不是(1e9)的平方
                anx += mini * mini;
        }
        cout << anx << endl;
    }
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值