[学习笔记] 求交互参数类交互问题
本篇文章参考 \(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\) 的位置,那么类似地我们可以用求和分析的方法:
所以如果我们询问位置集合 \(\{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
更不动了,我摆烂了