这是一道交互题。
作为一个熟练的 OI 选手,你对数据结构的各种题型早已轻车熟路,比赛中只要碰到数据结构题就能三下五除二轻松搞定。这一天,你翻开 OJ,看到了这道题:
给定 $n$ 个点的有根树,点编号为 $1, 2, \cdots, n$,$1$ 为根。每条边上有一个 $1$ 至 $n - 1$ 的两两不同的权值。维护一个数据结构,支持交换权值为 $x$ 和 $y$ 的边的权值,以及询问从根节点到 $u$ 号点的所有的边的权值最大值。
这种简单而经典的树上问题,对你已经是不在话下。厌倦了维护修改,回答询问的你,打算换一个角色。这回,你才是那个提问题的人,你需要构造合适的操作来套取题的数据。
具体地说,现在你并不知道树的结构,也不知道初始时树上每条边的权值,你需要通过如下两种操作得到树的结构和初. 始. 时. 树上每条边的权值:
- 给出正整数 $x, y$,其中 $1 \le x, y < n, x \neq y$,交换权值为 $x, y$ 的两条边的权值。
- 给出正整数 $u$,其中 $2 \le u \le n$,并得到从 $1$ 号点到 $u$ 号点的路径上所有边中权值的最大值。
题目保证树的形态和每条边的权值是预先给定的,不会根据你的操作而动态生成。
实现细节
你需要引用头文件 #include "ds.h"
。你可以调用以下函数与交互库进行交互:
void exchange(int x, int y);
- 这个函数对应操作 $1$,表示交换权值为 $x, y$ 的两条边的权值。
- 你需要保证 $1 \le x, y < n, x \neq y$。
int query(int u);
- 这个函数对应操作 $2$,返回从 $1$ 号点到 $u$ 号点的路径上所有边的权值的最大值。
- 你需要保证 $2 \le u \le n$。
void answer(std::vector<int> par, std::vector<int> val);
- 这个函数用来回答你所得到的答案,格式如下:
par
是一个长度为 $n - 1$ 的数组,其中par[i]
表示树上 $i + 2$ 号节点的父亲编号,其中 $0 \le i \le n - 2$。val
也是一个长度为 $n - 1$ 的数组,其中val[i]
表示树上 $i + 2$ 号节点到它父亲的边的初始权值,其中 $0 \le i \le n - 2$。
- 你需要调用该函数恰好一次!
你不需要,也不应该实现主函数。在本题中,你只需要实现如下函数:
void solve(int n, int lim1, int lim2);
- 其中,$n$ 表示树的点数,$lim1$ 表示操作 $1$ 的次数限制,$lim2$ 表示操作 $2$ 的次数限制。
- 最终测试时,对于每个测试点,交互库会调用恰好一次
solve
函数,并根据调用answer
函数的正误来评分。
在选手目录(题目附件)中,我们提供了 sample.cpp
供你参考,你可以在此基础上实现你的程序。
测试程序方式
本题目录下提供了 grader.cpp
文件。最终测试的交互库与下发交互库有不同,因此你的实现不应依赖交互库实现。
你需要将你的程序 ds.cpp
和 grader.cpp
、ds.h
放置在同一目录下,并在该目录下使用如下编译命令得到可执行程序 ds(.exe)
:
g++ -o ds ds.cpp grader.cpp -O2 -std=c++14 -lm
题目附件还提供了 compile.sh
,其内容为该编译命令。你可以运行它进行编译,也可以复制该文件中的编译命令进行编译。
可执行程序从标准输入读入以下格式的数据:
- 第一行包含三个正整数 $n, lim1, lim2$,表示树的点数,操作 $1$ 的限制次数和操作 $2$ 的限制次数。交互库保证可以处理 $2 \le n \le 500000$ 的情况,对于 $n > 500000$ 的情况不做正确运行保证。
- 第二行 $n - 1$ 个正整数 $p_2, p_3, \cdots, p_n$。其中 $p_i$ 表示 $i$ 号点的父亲的节点编号。你需要保证 $1 \le p_i \le n$ 且输入给出了合法的树的结构。
- 第三行 $n - 1$ 个正整数 $v_2, v_3, \cdots, v_n$。其中 $v_i$ 表示 $i$ 连向 $p_i$ 的边的权值。你需要保证 $v_2, v_3, \cdots, v_n$ 构成 $1$ 至 $n - 1$ 的一个排列。
- 在本地测试时,请务必保证你的输入格式符合要求,否则我们不保证交互库会正常运行。
如果你的输入合法且没有运行错误,下发的交互库将会根据你的调用输出如下信息:
- 如果某次
exchange
函数的调用违反了 $1 \le x, y < n, x \neq y$ 的限制,输出错误信息:Invalid call of exchange(x, y)!
。 - 如果
exchange
函数调用次数超过 $lim1$,输出错误信息:Too many exchanges!
。 - 如果某次
query
函数的调用违反了 $2 \le u \le n$ 的限制,输出错误信息:Invalid call of query(u)!
。 - 如果
query
函数调用次数超过 $lim2$,输出错误信息:Too many queries!
。 - 在输出任何以上错误信息后,交互库立即终止。
- 交互库会在你每次调用
answer
函数时输出提示信息:- 如果
par
或val
的长度不是 $n - 1$,则输出Invalid output!
。 - 如果
par
数组与树的形态不同,那么它会给出第一个错误的位置,并返回如下格式的错误信息:The answer to p[i] is wrong! The right answer is j, but you output k.
。注意,这里 $2 \le i \le n,j = p_i$ 为 $i$ 号点的真正的父亲编号,$k = $par[i - 2]
为你给出的位置编号。 - 如果
par
数组正确,但val
数组与初始时树上边权值不同,那么它会给出你第一个错误的位置,并返回如下格式的错误信息:The answer to v[i] is wrong! The right answer is j, but you output k.
。类似地,这里 $2 \le i \le n$,$j = v_i$ 为 $i$ 号点到它父亲的边的真正的初始权值,$k =$val[i - 2]
为你给出的权值。 - 如果你给出的
par
和val
数组正确,那么交互库会输出你调用exchange
函数和query
函数的次数。输出格式为:Right output! cnt1 = A, cnt2= B.
,其中 $A$ 表示你调用exchange
函数的次数,$B$ 表示你调用query
函数的次数。
- 如果
- 使用下发的交互库时你可以通过调用多次
answer
函数对你的程序进行测试。但对于你提交的代码,如果调用answer
函数超过 $1$ 次,便只能获得 $0$ 分。
你的程序不应该操作标准输入输出,否则视为攻击交互库。
输入格式
见【测试程序方式】。
输出格式
见【测试程序方式】。
样例一
input
3 100 100
1 2
2 1
output
Right output! cnt1 = 2, cnt2 = 5.
explanation
一种可能的输入如下:
- $2$ 号点的父亲是 $1$,$1$ 到 $2$ 的边初始权值为 $2$。
- $3$ 号点的父亲是 $2$,$2$ 到 $3$ 的边初始权值为 $1$。
一种可能的交互过程如下:
- 调用
query(2)
,返回 $2$。 - 调用
exchange(1, 2)
。此时,$1$ 到 $2$ 的边权值为 $1$,$2$ 到 $3$ 的边权值为 $2$。 - 调用
query(2)
,返回 $1$。此时,我们可以知道 $1$ 与 $2$ 直接相连。 - 调用
query(3)
,返回 $2$。 - 调用
exchange(1, 2)
。 - 调用
query(3)
,返回 $2$。此时,我们可以推出 $2$ 与 $3$ 直接相连。 - 调用
query(2)
,返回 $2$。此时,我们可以推出在两次exchange
操作之后,$1$ 到 $2$ 的边权值为 $2$,$2$ 到 $3$ 的边的权值为 $1$。 - 调用
answer([1, 2], [2, 1])
,结束程序。
样例二
见题目附件中的 ds2.in
与 ds2.ans
,这个样例满足 $n \le 50$ 和特殊性质 $\text A$ 的条件。
样例三
见题目附件中的 ds3.in
与 ds3.ans
,这个样例满足 $n \le 1000$。
样例四
见题目附件中的 ds4.in
与 ds4.ans
,这个样例满足 $n \le 20000$ 和特殊性质 $\text B$ 的条件。
样例五
见题目附件中的 ds5.in
与 ds5.ans
,这个样例满足 $n \le 100000$ 和特殊性质 $\text A$ 的条件。
样例六
见题目附件中的 ds6.in
与 ds6.ans
,这个样例满足 $n \le 100000$。
样例七
见题目附件中的 ds7.in
与 ds7.ans
,这个样例满足 $n \le 500000$。
子任务
子任务编号 | $n \leq$ | 特殊性质 | $lim1=$ | $lim2=$ | 分值 |
---|---|---|---|---|---|
$1$ | $\leq 50$ | A | $1\,000\,001$ | $1\,000\,001 $ | $6$ |
$2$ | $\leq 1\,000$ | 无 | $1\,100\,002$ | $2\,200\,002$ | $9$ |
$3$ | $\leq 10^5$ | A | $3\,000\,003$ | $3\,000\,003$ | $14$ |
$4$ | $\leq 20\,000$ | B | $3\,000\,004$ | $3\,000\,004$ | $14$ |
$5$ | $\leq 10^5$ | 无 | $3\,000\,005$ | $3\,000\,005$ | $21$ |
$6$ | $\leq 5 \cdot 10^5$ | A | $3\,500\,006$ | $3\,500\,006$ | $11$ |
$7$ | 无 | $3\,500\,007$ | $3\,500\,007$ | $25$ |
特殊性质 $\text A$:
- 每个节点有不超过 $1$ 个儿子,即树的形态是一条链;
- 链的非根节点标号在所有可能的标号中等概率随机;
- 边的权值排列在所有 $(n - 1)!$ 种可能的排列中等概率随机。
特殊性质 $\text B$:
- 树形态按如下方式随机生成:
- 先对每个 $2 \le i \le n$,令 $i$ 的父亲在 $[1, i - 1]$ 的整数之间等概率随机选取,
- 再等概率随机打乱非根节点的编号,得到最终的带标号有根树的结构。
- 边的权值排列在所有 $(n - 1)!$ 种可能的排列中等概率随机。
你可以根据 $lim1, lim2$ 的值来判断数据所满足的特殊性质。
评分方式
本题首先会受到和传统题相同的限制。例如编译错误会导致整道题目得 $0$ 分,运行时错误、超过时间限制、超过空间限制等会导致相应测试点得 $0$ 分等。你只能访问自己定义的和交互库给出的变量及其对应的内存空间,尝试访问其他空间将可能导致编译错误或运行错误。
在以上条件下,你能够拿到一个测试点的分数当且仅当你每次调用函数的参数格式正确,调用 exchange
函数的次数不超过 $lim1$ 次,调用 query
函数的次数不超过 $lim2$ 次,且第一次调用 answer
函数给出的 par
数组和 val
数组正确。
保证在下发的交互库和最终评测的交互库中,exchange
和 query
函数的单次最坏复杂度是 $O(\log n)$,在题目限制下使用时间不超过 $4$ 秒,最大空间占用不超过 $256$ MB。
也就是说,你至少有 $4$ 秒的时间和 $768$ MB 的空间可以使用。
提示
我们再次提醒;树的形态和每条边的权值是预先给定的,不会根据你的操作而动态生成。
你需要注意你的程序的时间开销和空间开销。
通过访问输入输出文件、攻击评测系统或攻击评测库等方式得分属于作弊行为,所得分数无效。
时间限制:$8\texttt{s}$
空间限制:$1\texttt{GB}$