河南省第十四届ICPC大学生程序设计竞赛—C结对编程

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 524288K,其他语言1048576K
64bit IO Format: %lld

题目描述

A公司是一家主营软件开发的公司。公司内有n名员工,编号为1到n的整数,除了1号老板外每名员工都有一个直接上级和若干个间接上级(上级的上级)。

这天A公司正在进行结对编程能力测试。为了尽量避免由于员工个人因素影响测试的结果,本次测试采用随机抽人的方式。具体方法是由公司的所有非空员工子集中等概率地取出一个子集。然后找出该子集的级别最低的共同上级作为团队指挥(1号老板的级别是最高的)。当然,该团队指挥有可能在也有可能不在该子集中,在子集中的话就参与结对编程,不在的话就只负责指挥。

由于你大学参加过ICPC比赛,编程能力比较强,公司派你写一段程序来完成这个选人的过程。但是你最近疏于编程训练,头脑不清晰,犯了个大错误。你的代码里居然真的直接等概率取出一个非空子集,却忽略了这样一个事实:选出的子集人数必须是偶数,才能安排接下来的结对编程活动!

但是事到如今也无法挽救了。你了解到公司里每个职员都有一个可以量化的权限,当职员i被选作团队指挥时,如果你的程序输出了一个合理的结果(偶数个人),他会奖励你aia_iai​元作为酬劳,反之如果你的程序输出了一个不合理的结果(奇数个人),那他会罚你aia_iai​元作为赔偿。

那么,请你为自己计算出:你期望会损失多少钱?为了避免浮点误差,请你输出期望损失的钱数×(2n−1)\times (2^n - 1)×(2n−1)的结果,可以证明这是个整数。同时,你的输出也有可能是个负数,此时说明期望会赚到钱。

输入描述:

第一行输入n,满足1≤n≤2×1051\le n \le 2\times 10 ^ 51≤n≤2×105代表公司内员工的个数。

其后一行n-1个整数,从左到右代表2号员工到n号员工的上级是几号。

其后一行n个整数,从左到右代表1号到n号员工的权限值aia_iai​ ,满足0≤ai<1050 \leq a_i < 10^50≤ai​<105

输出描述:

请输出一个整数,表示你期望损失的钱数×(2n−1)\times (2^n-1)×(2n−1)的结果。

示例1

输入

复制3 1 1 1 2 3

3
1 1
1 2 3

输出

复制4

4

说明

对于样例,有以下情况:

选出1,指挥为1,赔偿1元

选出2,指挥为2,赔偿2元

选出3,指挥为3,赔偿3元

选出1,2,指挥为1,赚1元

选出1,3,指挥为1,赚1元

选出2,3,指挥为1,赚1元

选出1,2,3,指挥为1,赔偿1元

最终损失的期望×(23−1)\times (2^3-1)×(23−1)=4

我的答案:(错误)

#include <iostream>
#include <vector>
#include <queue>
#include <cmath>

using namespace std;

// 计算每个节点的子树大小
void calculateSubtreeSizes(int node, const vector<vector<int>>& tree, vector<int>& subtree_size) {
    subtree_size[node] = 1;
    for (int child : tree[node]) {
        calculateSubtreeSizes(child, tree, subtree_size);
        subtree_size[node] += subtree_size[child];
    }
}

long long calculateExpectedLoss(int n, const vector<int>& supervisors, const vector<int>& permissions) {
    // 建立树结构
    vector<vector<int>> tree(n + 1);
    for (int i = 2; i <= n; ++i) {
        tree[supervisors[i - 2]].push_back(i);
    }
    
    // 计算每个节点的子树大小
    vector<int> subtree_size(n + 1, 0);
    calculateSubtreeSizes(1, tree, subtree_size);
    
    // 计算期望损失
    long long expected_loss = 0;
    for (int i = 1; i <= n; ++i) {
        if (subtree_size[i] % 2 == 0) {
            expected_loss -= permissions[i - 1];
        } else {
            expected_loss += permissions[i - 1];
        }
    }
    
    // 返回期望损失乘以 (2^n - 1)
    return expected_loss * (pow(2, n) - 1);
}

int main() {
    int n;
    cin >> n;
    
    vector<int> supervisors(n - 1);
    for (int i = 0; i < n - 1; ++i) {
        cin >> supervisors[i];
    }
    
    vector<int> permissions(n);
    for (int i = 0; i < n; ++i) {
        cin >> permissions[i];
    }
    
    long long result = calculateExpectedLoss(n, supervisors, permissions);
    cout << result << endl;
    
    return 0;
}

正确答案:

一、信息(题目的有用信息)

  1. 公司员工数目 n:范围为1到200,000。
  2. 每个员工的上级信息:从2号员工到n号员工,每个员工的直接上级编号。
  3. 每个员工的权限值:从1号到n号员工的权限值。
  4. 选出的子集人数必须是偶数:才能安排结对编程活动,否则会受到罚款。
  5. 计算期望损失的钱数乘以 (2^n - 1):输出为整数,可能为负值表示赚到钱。

二、分析

  1. 树的构建:题目给出的是一个树形结构,根节点为1号员工,其它员工都有一个直接上级。
  2. 权限值的作用:在选出的子集中,根据子集的奇偶性,权限值决定了奖励或罚款的金额。
  3. 子集的奇偶性影响:奇数人数的子集会导致罚款,偶数人数的子集会导致奖励。
  4. 计算子树大小:通过DFS遍历树,计算每个节点的子树大小,这有助于判断子集的奇偶性。
  5. 期望损失计算:根据子树大小,计算每个节点作为指挥的情况下,可能的期望损失或收益。

样例分析

示例1:

  • 输入:3 1 1 1 2 3
  • 解释:对于每个子集计算最低共同上级,并根据人数奇偶性计算损失和收益。

三、算法设计

  1. 读取输入:包括员工数量、上级信息和权限值。
  2. 构建树:根据上级信息,构建员工的树形结构。
  3. DFS计算子树大小:遍历树,计算每个节点的子树大小。
  4. 计算期望损失:遍历每个节点,计算其作为指挥时的损失或收益。
  5. 输出结果:将期望损失乘以 (2^n - 1) 并输出。

四、代码实现(用C++)

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

const int N = 2e5 + 5;
vector<int> v[N];
long long c[N];
int n, a[N];
long long d[N];
long long b[N];

// 深度优先搜索函数,用于计算每个节点的子树大小
void dfs(int x) {
    for(auto u : v[x]) {
        dfs(u);
        d[x] += d[u] + 1;  // 计算节点x的子树大小
    }
}

int main() {
    cin >> n;
    // 读取每个员工的上级信息
    for(int i = 2; i <= n; i++) {
        cin >> a[i];
        v[a[i]].push_back(i);  // 建立树结构
        b[a[i]]++;  // 统计每个节点的直接子节点数
    }
    // 读取每个员工的权限值
    for(int i = 1; i <= n; i++) {
        cin >> c[i];
    }
    // 计算每个节点的子树大小
    dfs(1);
    
    long long sum = 0;
    // 计算期望损失
    for(int i = 1; i <= n; i++) {
        sum += (1 - b[i]) * c[i];  // 权限值乘以(1 - 直接子节点数)
    }
    cout << sum;
    return 0;
}

五、实现代码过程中可能遇到的问题

  1. 输入输出问题:数据量大,输入输出需要高效处理。
  2. 树的构建错误:上级关系处理错误,导致树结构不正确。
  3. 子树大小计算错误:DFS递归处理不当,导致子树大小计算错误。
  4. 边界情况处理:如只有一个员工的情况。

六、debug

  1. 检查树构建:通过输出每个节点的子节点,确认树结构正确。
  2. 验证DFS计算:输出每个节点的子树大小,确认DFS递归计算正确。
  3. 边界测试:对最小和最大输入情况进行测试,确保算法正确性。
  4. 逐步调试:逐行调试关键步骤,如树构建、DFS计算和期望损失计算,确保逻辑正确。

七、错误原因

错误分析

  1. 错误代码的逻辑

    • 之前的错误代码试图通过计算每个员工的子树大小并使用这些子树大小来判断奇偶性,然后计算期望损失。
    • 期望损失计算通过遍历所有员工,根据子树大小的奇偶性来更新期望损失。
  2. 错误原因

    • 子树大小的奇偶性不能直接用于判断期望损失的计算,因为我们需要考虑子集人数的奇偶性,这与子树大小的奇偶性并不直接相关。
    • 没有考虑到每个节点的直接子节点数量对期望损失的影响。

  • 40
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏驰和徐策

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值