[学习笔记] 求交互参数类交互问题

本篇文章参考 \(2022\) 国家集训队论文《浅谈求交互参数类问题》,主要分析其中的例题,并添加详解和代码。

1 交互参数类问题的基本考法

这类问题是指:交互库有一个隐藏的交互参数,你可以通过不断向交互库提出询问来得到这个交互参数。

流行的交互方式通常是函数式交互,代码通常不需要实现主函数,只需要实现求解交互参数特定的函数。

常常要在头文件处添加 #include "name.h",然后输入命令行 g++ grader.cpp name.cpp -o name -O2 -lm 就可以得到可执行文件,对熟悉 vscode 等编辑器的同学,这并不是一件难事。

我还不会的操作:交互环境下测试极限数据;生成大量数据进行类似"对拍"的操作(有没有大佬教教我啊😭)

2 逐步增加方便利用的已知信息

在一道题的开始,可以认为已知信息为空,或者选定一个元素作为初始的已知信息。然后在每一次操作中,目标都是将可以利用的已知信息变多

那么请你带着这样的问题继续阅读:在不同的问题环境下,什么信息是方便利用的?什么信息是不方便利用的?

例1 [IOI2018] 组合动作

题目描述

点此看题

解法

如果我们知道了 \(s\) 的一个前缀 \(s_p\),那么如果询问 \(s_pa\) 返回的是 \(p+1\),那么说明 \(s_{p+1}=s_p+a\)

先通过两次询问确定第一位,对于 \(p\in[1,n-1)\),我们询问 \(s_pa+s_pba+s_pbb+s_pbc\),发现此时有三种返回值:

  • 如果返回值 \(=p\),说明 \(s_{p+1}=s_p+c\)
  • 如果返回值 \(=p+1\),说明 \(s_{p+1}=s_p+a\)
  • 如果返回值 \(=p+2\),说明 \(s_{p+1}=s_p+b\)

可以通过两次询问确定最后一位,所以总询问次数是 \(2+n-2+2=n+2\)

总结

你发现了吗?在本题的语境下,方便利用的信息是这个序列的一个前缀;但是如果你只知道零散几个位置的值,并不方便利用,所以这就是不方便利用的信息。

在思考解法之前,先思考清楚这个问题,可以帮助你确定做题的大方向。

此外,交互题和构造法脱不了干系,巧妙的构造可以帮助你压缩交互次数,那么我们就一定要观察清楚交互方式的特点。

例一的评测记录

例2 [JOISC2017 Day3] 自然公园

题目描述

点此看题

解法

我们维护一个连通块 \(s\),初始时只包含 \(0\) 号节点,考虑点和联通块 \(s\) 直接有边相连的点 \(x\),找出它们之间的边。

考虑分治,我们先求出 \(s\) 的一个 \(\tt dfs\) 序,然后二分 \(\tt dfs\) 序的一个前缀,这样前缀一定是一个连通块。我们把这个前缀拿出来和点 \(x\) 询问,如果为真就说明前缀和 \(x\) 有边。设满足条件的最小前缀是 \(p\),那么我们可以确定边 \((p,x)\)

但是可能还有其它的连边,所以我们把 \(p\)\(s\) 中的其他边断开,然后对于每个连通块分别求解即可。由于每个点的度数最多为 \(7\),所以询问次数的上界是 \(7m\log n\),但是这个界远远跑不满。

现在的问题变成了找到这个相连的点 \(x\),我们可以随便从一个点 \(i\) 出发,找到 \(i\) 到这个连通块的任意一条链。我们每次可以通过二分,找到链的前驱节点 \(y\),判断条件是排除已经在链上的节点后,加入 \(s\) 中和编号在 \([0,y]\) 中的节点,\(i\) 是和连通块 \(s\) 联通的,那么最小满足条件的 \(y\) 就是前驱节点,这部分询问次数 \(n\log n\)

总结

在本题的语境下,方便利用的是你找到的一个点集的导出子图;但如果你只知道零散的几条边,这是不好利用的,甚至还可能导致重复求出某些信息,这就是不方便利用的信息。

交互方式为集合的交互题,可以考察分治优化,常见的形式就是求出最小满足条件的前缀,考虑它会带来一些方便利用的信息(比如 \(\tt NOI2019\) 真题:I 君的探险

例二的评测记录

例3 CF1299E So Mean

题目描述

点此看题

解法

已经开始人类智慧了,真的是极其劝退,我还在考虑要不要记录把这个论文读下去

首先我们考虑一个简单的问题:如何确定 \(1/n\) 的位置?\(\frac{n(n+1)}{2}=\frac{n(n-1)}{2}+n=1\bmod n-1\),那么如果我们询问位置集合 \(\{1,2...n\}-\{x\}\) 的返回值是 \(1\)\(p_x=1/n\),可以把先访问到的定为 \(1\),后访问到的定为 \(n\),因为有两种可能的序列,它们都是合法的。

可以把上面的做法推广,假设我们已经确定了 \(1...k\)\(n-k+1...n\) 这些值的位置,考虑确定 \(k+1\)\(n-k\) 的位置,那么类似地我们可以用求和分析的方法:

\[\frac{(n-2k)(n+1)}{2}=\frac{(n-2k)(n-2k-1)}{2}+(k+1)(n-2k)=k+1\bmod n-2k-1 \]

所以如果我们询问位置集合 \(\{k+1...n-k\}-\{x\}\) 的返回值是 \(1\),就说明 \(p_x=k+1/n-k\),此时可以通过和 \(1\) 询问确定它们的奇偶性,就能确实具体是哪一个了,询问次数 \(O(n^2)\)

这样做的瓶颈就是每次扩增时访问的点数太多,考虑利用模某个值的余数来初步筛选可能成为 \(k+1/n-k\) 的位置。一开始可以知道每个位置模 \(2^1\) 的值,考虑倍增,假设我们已经知道了 \(p_i\)\(2^k\) 的值是 \(r_i\),现在想要求出 \(p_i\)\(2^{k+1}\) 的值 \(r'_{i}\)

我们找到值域集合 \(\{1,2...2^{k+1}\}-\{r_i\}\) 的对应的位置集合加上 \(\{i\}\),并询问它。如果结果是 \(1\),那么说明 \(r'_i=r_i+2^k\);如果结果是 \(0\),那么说明 \(r_i'=r_i\)

询问次数大概是 \(3n\log n\) 的,可能常数较小,并不容易卡满。

总结

本题体现出了确定初始信息的重要性,序列问题中预先确定 \(1,n\) 可能是一种好方法。并且确定初始信息的方法是具有启发性的,可能会帮助我们走向正解。

倍增常常和分治是紧密相关的,我们可以换一个角度理解:在第一次询问之后,我们把所有数分成奇数和偶数,就将数据规模变成了 \(\frac{1}{2}\);再根据 \(\bmod 4\) 分成 \(4\) 部分,数据规模就变成了 \(\frac{1}{4}\)

复杂度满足递归式 \(T(n)=2\cdot T(\frac{n}{2})+O(n)=O(n\log n)\)

例三的评测记录

例4 [WC2018] 即时战略

题目描述

点此看题

解法

草调柠檬一个小时,全局变量改变的时候一定要注意存下来啊!

首先考虑暴力方法怎么做,我们把 \(1\) 当成根节点开始拓展,每次访问所有剩余的节点来获得所有与它有直接连边的点。

优化考虑每次加入一个点,那么我们要打通它到已知连通块的之间的链,所以我们要快速确定这条链是从连通块上的哪个节点导出的,就可以直接把这条链添加到连通块上面。

在树上确定节点的问题,可以考虑用分治结构优化寻找的过程。考虑维护一棵点分树,在定位节点的时候从根开始访问,设现在到的点是 \(x\),要添加的点是 \(y\),调用 explore(x,y),设结果是 \(z\),如果 \(z\) 没有出现过,那么就说明我们成功定位了,可以继续添加链。否则我们找到 \(z\) 所在的分治子树,把 \(x\) 移动到该分治子树中的第一个儿子即可。

由于点分树的深度是 \(O(\log n)\) 的,所以询问次数 \(n\log n\),剩下的问题是如何动态地维护点分树。添加一个叶节点后,可以利用替罪羊树的思想,在重儿子大小 \(>0.7\) 的子树大小时重构整颗子树,时间复杂度 \(O(n\log^2 n)\)

总结

本题最终化归到了一个树上定位问题,可以考虑使用一些树上分治结构来优化(类似的题目还有:Nauuo and Binary Tree

例四的评测记录

优化方案

这一部分我觉得论文已经写得比较深入了,我们先来看看作者是怎么总结的:

要找到优化方案,要先对先前的算法有更深入的理解,通过归纳总结,多数情况下我们使用的算法就是重复执行下面两步:

  • 找到一个未知的元素。
  • 找到这个元素与已知信息中元素的关系。

很多时候,这两个步骤是可以优化的,和优化数据结构题类似,可以先找到复杂度的瓶颈,然后针对这一瓶颈套用常用的 \(\log\) 或者根号算法:分治、分块等。

例如,如果瓶颈在第一步,可以试着对没有确定的部分进行分治,将他们根据先前询问的特性分成若干部分,然后分别处理;如果瓶颈在于第二步,则可以试着将已经确定的部分划分为若干部分,依次判断是否和将要加入的元素存在关系,然后只保留存在关系的部分。

个人认为分治法在交互题中的使用是极其多变,但是在各种情境下又有套路可循,下面我来尝试总结一下:

  • 交互方式为集合的题,可以搞出某个顺序,然后二分,那么满足某个条件的前缀可能就是我们想要的东西:自然公园模拟赛4-B;还可以把这个过程套上整体二分:I 君的探险
  • 巧妙地利用二进制,逐位确定法:模拟赛4-B;倍增来把元素分类:So Mean
  • 树上交互的题目,可以利用树上的分治结构优化:即时战略Nauuo and Binary Tree

3 尝试找到唯一的前驱

接下来我们介绍第二种方法,这一种方法是针对每一个元素的:找到前驱,让每一个点都与它的前驱相连,这样就可以得到一条链。在序列上,一个数的前驱可以认识是它前面的那一个数,在一棵树上,可以认为是一个点的父节点。

归根到底,这种方法的目的在于找到一条链,而之前介绍的方法目的是找出一个连通块。

下面这两种情况可以优先考虑使用这种方法:需要找到一条链,链的末端是已知的但是无法由末端回溯;或是答案是一条链,且其他方法不能较好的处理。

有时并不能直接的找到一个点 \(u\) 的前驱 \(p(u)\),但是能找到 \(p^k(u)\),只要满足以下条件就可以在链长次数内还原出链:

  • 如果存在 \(p(u)\),就一定能找到一个 \(p^k(u)\)
  • 对于任意 \(v=p^x(u),x\not=1\),一定能找到 \(p^y(u),1\leq y<x\)

即可以找到任意 \(u\) 前面的点,能找到任意 \(u,v\) 中间的点,就可以还原出链;先找到 \(k=p^x(u)\),然后处理 \(k\)\(v\) 之间的链,然后处理 \(u\)\(k\) 之间的链。

例题 5 [集训队互测2021] 整数

先只能鸽了,论文根本看不懂,网上也没有评测资源。

4 巧妙处理交互库寄存器

交互库有时候还会存在一个寄存器,所以连续两次相同的询问可能会出现不同的答案,这会对分析造成极大的影响。

还有一种情况,交互库寄存器基于交互参数,最终对交互库寄存器的终态有要求,本质上还是求出交互参数。

例题 6 [NEERC2016] Indiana Jones and the Uniform Cave

更不动了,我摆烂了

例题 7 Rotary Laser Lock

更不动了,我摆烂了

posted @ 2022-05-25 15:56  C202044zxy  阅读(165)  评论(1编辑  收藏  举报