#P9075. [WC/CTS2023] 树据结构

    ID: 8183 远端评测题 8000ms 1024MiB 尝试: 0 已通过: 0 难度: 7 上传者: 标签>WC/CTSC/集训队2023交互题O2优化

[WC/CTS2023] 树据结构

题目背景

这是一道交互题。

在提交本题前请务必仔细阅读以下内容。

本题只支持 C++ 语言提交(建议使用 C++14,请不要使用 C++14 (GCC9))。

由于洛谷特殊的交互机制,在提交本题时,请去掉代码中的 #include "ds.h" 语句,并将以下内容粘贴到代码最开头,然后提交:

#include <vector>
void exchange(int x, int y);
int query(int u);
void answer(std::vector<int> par, std::vector<int> val);

如果您在提交本题时出现了任何意外的情况,请咨询管理员。

题目描述

作为一个熟练的 OI 选手,你对数据结构的各种题型早已轻车熟路,比赛中只要碰到数据结构题就能三下五除二轻松搞定。这一天,你翻开 OJ,看到了这道题:

给定 nn 个点的有根树,点编号为 1,2,,n1, 2, \dots, n11 为根。每条边上有一个 11n1n - 1两两不同的权值。维护一个数据结构,支持交换权值为 xxyy 的边的权值,以及询问从根节点到 uu 号点的所有的边的权值最大值。

这种简单而经典的树上问题,对你已经是不在话下。厌倦了维护修改,回答询问的你,打算换一个角色。这回,你才是那个提问题的人,你需要构造合适的操作来套取题的数据。

具体地说,现在你并不知道树的结构,也不知道初始时树上每条边的权值,你需要通过如下两种操作得到树的结构和 初始时 树上每条边的权值:

  1. 给出正整数 x,yx, y,其中 1x,y<n1 \le x, y < nxyx \neq y,交换 权值为 x,yx, y 的两条边的权值。
  2. 给出正整数 uu,其中 2un2 \le u \le n,并得到从 11 号点到 uu 号点的路径上所有边中权值的最大值。

题目保证树的形态和每条边的权值是预先给定的,不会根据你的操作而动态生成。

【实现细节】

你需要引用头文件 #include "ds.h"。你可以调用以下函数与交互库进行交互:

void exchange(int x, int y);
  • 这个函数对应操作 11,表示交换权值为 x,yx, y 的两条边的权值。
  • 你需要保证 1x,y<n1 \le x, y < nxyx \neq y
int query(int u);
  • 这个函数对应操作 22,返回从 11 号点到 uu 号点的路径上所有边的权值的最大值。
  • 你需要保证 2un2 \le u \le n
void answer(std::vector<int> par, std::vector<int> val);
  • 这个函数用来回答你所得到的答案,格式如下:
    • par 是一个长度为 n1n - 1 的数组,其中 par[i] 表示树上 i+2i + 2 号节点的父亲编号,其中 0in20 \le i \le n - 2
    • val 也是一个长度为 n1n - 1 的数组,其中 val[i] 表示树上 i+2i + 2 号节点到它父亲的边的初始权值,其中 0in20 \le i \le n - 2
  • 你需要调用该函数恰好一次!

你不需要,也不应该实现主函数。在本题中,你只需要实现如下函数:

void solve(int n, int lim1, int lim2);
  • 其中,nn 表示树的点数,lim1lim1 表示操作 11 的次数限制,lim2lim2 表示操作 22 的次数限制。
  • 最终测试时,对于每个测试点,交互库会调用恰好一次 solve 函数,并根据调用 answer 函数的正误来评分。

在题目附件中,我们提供了 sample.cpp 供你参考,你可以在此基础上实现你的程序。

【测试程序方式】

题目附件中提供了 grader.cpp 文件。最终测试的交互库与下发交互库有不同,因此你的实现不应依赖交互库实现。

你需要将你的程序 ds.cppgrader.cppds.h 放置在同一目录下,并在该目录下使用如下编译命令得到可执行程序 ds(.exe)

g++ -o ds ds.cpp grader.cpp -O2 -std=c++14 -lm

题目附件还提供了 compile.sh,其内容为该编译命令。你可以运行它进行编译,也可以复制该文件中的编译命令进行编译。

可执行程序从标准输入读入以下格式的数据:

  • 第一行包含三个正整数 n,lim1,lim2n, lim1, lim2,表示树的点数,操作 11 的限制次数和操作 22 的限制次数。交互库保证可以处理 2n5000002 \le n \le 500000 的情况,对于 n>500000n > 500000 的情况不做正确运行保证。
  • 第二行 n1n - 1 个正整数 p2,p3,,pnp_2, p_3, \dots, p_n。其中 pip_i 表示 ii 号点的父亲的节点编号。你需要保证 1pin1 \le p_i \le n 且输入给出了合法的树的结构。
  • 第三行 n1n - 1 个正整数 v2,v3,,vnv_2, v_3, \dots, v_n。其中 viv_i 表示 ii 连向 pip_i 的边的权值。你需要保证 v2,v3,,vnv_2, v_3, \dots, v_n 构成 11n1n - 1 的一个排列。
  • 在本地测试时,请务必保证你的输入格式符合要求,否则我们不保证交互库会正常运行。

如果你的输入合法且没有运行错误,下发的交互库将会根据你的调用输出如下信息:

  • 如果某次 exchange 函数的调用违反了 1x,y<n1 \le x, y < nxyx \neq y 的限制,输出错误信息:Invalid call of exchange(x, y)!
  • 如果 exchange 函数调用次数超过 lim1lim1,输出错误信息:Too many exchanges!
  • 如果某次 query 函数的调用违反了 2un2 \le u \le n 的限制,输出错误信息:Invalid call of query(u)!
  • 如果 query 函数调用次数超过 lim2lim2,输出错误信息:Too many queries!
  • 在输出任何以上错误信息后,交互库立即终止。
  • 交互库会在你每次调用 answer 函数时输出提示信息:
    • 如果 parval 的长度不是 n1n - 1,则输出 Invalid output!
    • 如果 par 数组与树的形态不同,那么它会给出第一个错误的位置,并返回如下格式的错误信息:The answer to p[i] is wrong! The right answer is j, but you output k.。注意,这里 2in2 \le i \le nj=pij = p_iii 号点的真正的父亲编号,k=k = par[i - 2] 为你给出的位置编号。
    • 如果 par 数组正确,但 val 数组与初始时树上边权值不同,那么它会给出你第一个错误的位置,并返回如下格式的错误信息:The answer to v[i] is wrong! The right answer is j, but you output k.。类似地,这里 2in2 \le i \le nj=vij = v_iii 号点到它父亲的边的真正的初始权值,k=k = val[i - 2] 为你给出的权值。
    • 如果你给出的 parval 数组正确,那么交互库会输出你调用 exchange 函数和 query 函数的次数。输出格式为:Right output! cnt1 = A, cnt2= B.,其中 AA 表示你调用 exchange 函数的次数,BB 表示你调用 query 函数的次数。
  • 使用下发的交互库时你可以通过调用多次 answer 函数对你的程序进行测试。但对于你提交的代码,如果调用 answer 函数超过 11 次,便只能获得 00 分。

你的程序不应该操作标准输入输出,否则视为攻击交互库。

输入格式

见【测试程序方式】。

输出格式

见【测试程序方式】。

3 100 100
1 2
2 1
Right output! cnt1 = 2, cnt2 = 5.
见附件中的 ds2.in
见附件中的 ds2.ans
见附件中的 ds3.in
见附件中的 ds3.ans
见附件中的 ds4.in
见附件中的 ds4.ans
见附件中的 ds5.in
见附件中的 ds5.ans
见附件中的 ds6.in
见附件中的 ds6.ans
见附件中的 ds7.in
见附件中的 ds7.ans

提示

【样例解释 #1】

一种可能的输入如下:

  • 22 号点的父亲是 111122 的边初始权值为 22
  • 33 号点的父亲是 222233 的边初始权值为 11

一种可能的交互过程如下:

  • 调用 query(2),返回 22
  • 调用 exchange(1, 2)。此时,1122 的边权值为 112233 的边权值为 22
  • 调用 query(2),返回 11。此时,我们可以知道 1122 直接相连。
  • 调用 query(3),返回 22
  • 调用 exchange(1, 2)
  • 调用 query(3),返回 22。此时,我们可以推出 2233 直接相连。
  • 调用 query(2),返回 22。此时,我们可以推出在两次 exchange 操作之后,1122 的边权值为 222233 的边的权值为 11
  • 调用 answer([1, 2], [2, 1]),结束程序。

【样例解释 #2】

这个样例满足 n50n \le 50 和特殊性质 A 的条件。

【样例解释 #3】

这个样例满足 n1000n \le 1000

【样例解释 #4】

这个样例满足 n20000n \le 20000 和特殊性质 B 的条件。

【样例解释 #5】

这个样例满足 n100000n \le 100000 和特殊性质 A 的条件。

【样例解释 #6】

这个样例满足 n100000n \le 100000

【样例解释 #7】

这个样例满足 n500000n \le 500000

【数据范围】

测试点编号 n=n= lim1=lim1= lim2=lim2= 特殊性质
11 1010 10000011000001 A
22
33 5050 10000021000002
44
55 600600 30000033000003
66
77 10001000 11000041100004 22000042200004
88 11000051100005 22000052200005
99
1010 2000020000 30000063000006 B
1111
1212
1313 100000100000 30000073000007 A
1414
1515 30000083000008
1616
1717
1818 500000500000 35000093500009 A
1919
2020 35000103500010 B
2121
2222 35000113500011
2323
2424
2525

特殊性质 A:

  • 每个节点有不超过 11 个儿子,即树的形态是一条链;
  • 链的非根节点标号在所有可能的标号中等概率随机;
  • 边的权值排列在所有 (n1)!(n - 1)! 种可能的排列中等概率随机。

特殊性质 B:

  • 树形态按如下方式随机生成:
    • 先对每个 2in2 \le i \le n,令 ii 的父亲在 [1,i1][1, i - 1] 的整数之间等概率随机选取,
    • 再等概率随机打乱非根节点的编号,得到最终的带标号有根树的结构。
  • 边的权值排列在所有 (n1)!(n - 1)! 种可能的排列中等概率随机。

你可以根据 lim1,lim2lim1, lim2 的值来判断数据所满足的特殊性质。

【评分方式】

本题首先会受到和传统题相同的限制。例如编译错误会导致整道题目得 00 分,运行时错误、超过时间限制、超过空间限制等会导致相应测试点得 00 分等。你只能访问自己定义的和交互库给出的变量及其对应的内存空间,尝试访问其他空间将可能导致编译错误或运行错误。

在以上条件下,你能够拿到一个测试点的分数当且仅当你每次调用函数的参数格式正确,调用 exchange 函数的次数不超过 lim1lim1 次,调用 query 函数的次数不超过 lim2lim2 次,且第一次调用 answer 函数给出的 par 数组和 val 数组正确。

保证在下发的交互库和最终评测的交互库中,exchangequery 函数的单次最坏复杂度是 O(logn)O(\log n),在题目限制下使用时间不超过 44 秒,最大空间占用不超过 256256 MB。

也就是说,你至少有 44 秒的时间和 768768 MB 的空间可以使用。

【提示】

我们再次提醒:树的形态和每条边的权值是预先给定的,不会根据你的操作而动态生成。

你需要注意你的程序的时间开销和空间开销。

通过访问输入输出文件、攻击评测系统或攻击评测库等方式得分属于作弊行为,所得分数无效。