非传统题与随机算法
 \(\text{非传统题与随机算法}\) 
\(\text{1 通信题}\)
\(\text{1.1 单轮通信题}\)
这类通信题一般指,仅进行一次通信的通信题,这也是大多数通信题的形式。
一种通信策略是对信息的(有损)压缩,你可以在保证一定正确率的情况下只传递部分信息。
另一种策略是将信息转化成另一种形式,比如哈希。
在实际问题中,需要关注对信息的需求,分析哪些信息是冗余的,这往往是解决通信题的关键。
\(\text{例题 1.1.1:Multiple Communications}\)
https://qoj.ac/contest/997/problem/4675
题意:
给定两个长度为 \(100\) 的序列 \(a,b\),序列中的每个元素是 \(1000\) 位的二进制数。A 只能看到 \(a\),他只能向 C 发送 \(3000\) 个二进制位,类似的,B 只能看到 \(b\),他也只能向 C 发送 \(3000\) 个二进制位。
C 要根据收到的信息回答 \(100\) 个问题,每个问题给定一个二进制数 \(c\),他需要回答正整数 \(x,y\) 满足 \(a_x\ \text{xor}\ b_y = c\),回答对 \(96\) 个问题即算 AC。
解法:
\(100\) 个 \(1000\) 位二进制数,总信息量为 \(100000 \ \text{bits}\),但是我们只能发送 \(3000\ \text{bits}\)。
考虑一下有损压缩,我们从 \(1000\) 位中随机选择 \(30\) 位,每个数只保留这些数位后传递。
看起来这样很可行,实际上如果这 \(100\) 个数中,前 \(992\) 个数位都是相同的,只有后 \(8\) 位不同,正确率会大大降低。
再考虑一下哈希,一个二进制数可以看成一个集合,异或运算可以看成集合的对称差运算,而对集合的哈希可以考虑 xor-hashing,即,对每一个数 \(x\) 随机一个 \([0,2^{30}-1]\) 的权值 \(w(x)\),一个集合 \(S\) 的哈希值 \(H(S)\) 定义为 \(\text{xor}_{x \in S} \ w(x)\)。可以直观的感受到,任取一个集合,它的哈希值可以近似看做 \([0,2^{30}-1]\) 里的一个随机数。
由于异或存在消去律,令两个集合 \(S,T\) 的对称差为 \(S \sqcup T\),则有 \(H(S \sqcup T) = H(S)\ \text{xor}\ H(T)\)。
回到题目中,我们将题目里的所有二进制数看成集合,异或看成对称差运算,A 和 B 共用同一个 \(w\) (比如使用相同种子的 mt19937 依次生成 \(w(1),w(2),\cdots,w(1000)\),或者本地生成好后打表),对每个集合计算哈希值即可。
\(\text{例题 1.1.2:Mapa}\)
https://qoj.ac/contest/1271/problem/6669
题意:
A 有 \(N\) 个正整数二元组,第 \(i\) 个二元组为 \((x_i,y_i)\),保证 \(x_i\) 互不相同,A 要发送 \(K\) 个二进制位给 B。
B 在收到信息后,需要回答 \(Q\) 个问题,每个问题给定一个 \(x_0\),B 需要回答一个正整数 \(y_0\) 使得存在一个二元组 \((x_0,y_0)\),保证一定有解,即 \(x_0\) 在 \(x_1,x_2,\cdots,x_N\) 中出现过。
\(1 \le N,Q \le 100, \ 1 \le x_i,y_i \le 10^9\),\(K \le 3000\) 即可通过。
解法:
直觉上特别违背信息论,因为只能传递 \(3000\) bits,而传递 \(y_i\) 已经需要 \(3000\) bits。
由于 B 在回答问题的时候知道了 \(x_0\),我们不妨思考一下是否有必要传递 \(x_i\)。
先考虑有损压缩,尝试随机删掉所有 \(x_i\) 的某一个二进制位,只要不出现两个相同的 \(x_i\),就可以一直删,删完之后只需要额外传递 \(30\) bits 表示哪些位被删掉了,此做法可以得到 \(58\) 分左右。
但是我们的目标是满分,我们不能在 \(x\) 上花费任何的代价。
假设 B 知道了所有 \(x_i\),那么 A 只需要按 \(x_i\) 排序后传递 \(y_i\)。思考一下能否将给定的二元组转化成一些固定的 \(x_i\),比如将所有 \(x_i\) 变成 \([1,N]\) 的排列。
拉格朗日插值!
\(N\) 个二元组唯一确定了一个 \(N-1\) 次多项式,将这个多项式求出来,传递 \([1,N]\) 的点值(传系数也行),插值一下就能求出 \(y_0\),由于插值有除法,可以在模 \(10^9+7\) 下算,需要的位数不变。
\(\text{例题 1.1.3:Ancient Machine}\)
https://qoj.ac/contest/878/problem/3098
题意:
给定一个长度为 \(N\) 的,只包含 X,Y,Z 的字符串,你可以不断的删去一个字符,如果选择了一个 Y,且它左边是 X,右边是 Z,你会得 \(1\) 分,其它情况不得分。构造一个删除的方案,最大化得分。
A 和 B 都知道 \(N\),但 A 知道字符串,B 不知道。A 可以向 B 发送 \(L\) 个 bits,B 在收到信息之后,需要构造出方案。
$3 \le N \le 10^5,\ L $ 不超过 \(70000\) 即为满分。
解法:
按照这节的引言所说,首先我们需要明确 B 需要什么,不妨简化一下问题,如果 B 知道了所有信息怎么做。
将最左边的 X 和最右边的 Z 拿出来,只有中间那一段有用。对于一个同字符的极长连续段,其中也只有一个有用。也就是说字符串一定可以被转化成 XY?Y?Y?...?YZ 的形式。观察到其中一定存在子串 XYZ,将这个 Y 删去,然后删去两边之一(注意不要把头尾删去),就能得到一个子问题。
一个特别简洁的方法是,取最左边的 Z,然后从这个 Z 前面的位置不断往前删,直到前面只剩下一个开头 X(注意这个 X 不要删掉),然后删去这个 Z,取下一个 Z 进行相同的步骤。这个做法只需要知道所有 Z  的位置。进一步观察,发现只需要知道每一个 Z 的连续段的最后一个位置就行。
也就是说,我们发送的长度为 \(N\) 的 \(01\) 串不存在相邻的 \(1\),考虑计算一共有多少长度为 \(N\) 的串,写出 dp 式子发现就是斐波那契数列的第 \(N\) 项,小于 \(2^{70000}\),理论可行,但是数字太大了,算起来很慢,退而求其次,考虑分块,枚举一些块长,发现:
将串每 \(63\) 个分一块,用 \(44\) bits 传递就行。
至于如何实现?我们可以计算出一个串是字典序第 \(rank\) 小的串,传递 \(rank\),解密的时候,类似线段树二分,可以根据 \(rank\) 是否小于等于填 \(0\) 后的方案数,确定每一位是 \(0\) 还是 \(1\)。
\(\text{习题 1.1:}\)
- https://yundouxueyuan.com/p/YDSP2023D1BonusB
- https://qoj.ac/contest/1782/problem/9237
- https://qoj.ac/contest/1633/problem/8642
$\text{1.2 实时通信题} $
与 \(1.1\) 相对,这类通信题中,各方可以进行在运算过程中实时通信。
大多数情况下,一方充当“交互库”的角色,解决问题的关键是如何设计另一方的问题,即,我们首先需要明确另一方需要什么,然后问题就转化成了 \(1.1\) 的形式,我们需要尽可能高效的回答另一方的问题。
下面这个例题中,双方互相充当对方的“交互库”。
\(\text{例题 1.2.1:Two Transportations}\)
题意:
A 手上有一张 \(N\) 个点 \(M_A\) 条边的无向带权图 \(G_A\),B 手上有一张 \(N\) 个点 \(M_B\) 条边的无向带权图 \(G_B\)。
A 想知道,若考虑所有 \(M_A+M_B\) 条边,即把两张图“并起来”后,节点 \(0\) 到每个点的最短路。A 和 B 可以实时通信。
\(1 \le N \le 2000, \ 0 \le M_A,M_B \le 5 \times 10^5, 1 \le 边权 \le 500\),总发送量不得超过 \(58000\) bits。
解法:
考虑 Dijkstra 的过程,每次找到一个距离最小的点去松弛,如何知道最小的点?我们可以令 A 和 B 把最小的距离发给对面,如果发现自己的距离小,就将对应点的编号发给对面,这样双方都知道最小的点了。
还有一个问题是,这个距离可能会达到 \(O(NW)\) 级别,\(W\) 是边权范围。令 \(M\) 为目前已知点中距离的最大值,那么距离一定在 \([M,M+W]\) 之间,传递的时候减去 \(M\) 再发送,一轮松弛传递的比特数量为 \(2 \log W+\log n=29\),总共不超过 \(29n=58000\) 比特,可以通过。
\(\text{习题 1.2:}\)
\(\text{1.3 加密通信}\)
有些题目交互库是自适应的,也就是说 A 和 B 的通信,会被很“聪明”的交互库干扰,一般来说这个交互库真的很聪明,以至于在假设交互库的策略均匀随机的情况下,即使有着极高的正确率,依然无法通过。
但是,你不让交互库看出来你在说什么,不就行了吗?😂
\(\text{例题 1.3.1:Magic Show }\)
https://qoj.ac/contest/1684/problem/8726
题意:
A 和 B 参与一个游戏,他们被关在两个房间中,不能相互通信,游戏的步骤如下:
- A 选择一个整数 \(n \in [2,5000]\) 告诉主持人 C。
- C 告诉 A 一个整数 \(X \in [1,10^{18}]\)。
- A 生成一个 \(n\) 个节点的有标号的无向无根树,告诉 C。
- C 从树中删去至多 \(\lfloor (n-2)/2 \rfloor\) 条边,并将剩余的边告诉 B。
- B 根据这棵残缺的数猜出 \(X\)。
解法:
我们不妨传递一条链,A 和 B 事先商量好一个随机排列 \(P\),A 将所有节点 \(u\) 变为 \(P(u)\) 后传递,B 接收到信息后将所有节点 \(u\) 变为 \(P^{-1}(u)\) 后,即得到原来的链。
在 C 的视角中,他收到的信息可以近似的看成一个随机排列,他不知道在说什么,只能闭眼乱删一半的边。
取 \(n=4800\),其中令 \([1,1200]\) 为 I 类点,\([1201,2400]\) 为 II 类点,\([2401,4800]\) 为 III 类点。对于 \(X\) 的每一个二进制位,分配 \(20\) 个 I 类点(即传递 \(20\) 次,因为 C 的策略已经退化成随机删边,一个 bit 的正确率为 \(1-2^{-20}\),已经足够大),如果这一位是 \(0\),我们不管它,如果这一位是 \(1\),我们将每一个 I 类点连上一个 II 类点,最后用所有 III 类点把它们串起来。
B 接收到信息之后,只需要看每一个二进制位,是否存在至少一个 II 类点相邻,即可确定这一位是什么。
这题还有很多离谱做法,比如把节点 \(i\) 连到 \((x \bmod (i-1))+1\),直观上你不可能从 \(5000\) 个数中选出 \(2500\) 个数使得它们的 \(\text{lcm} \le 10^{18}\)。
或者,对于所有点,如果代表的数位是 \(0\),就挂在 \(1\) 上,如果代表的是 \(1\),就挂在 \(2\) 上,但这样交互库可以把挂在某个点上的所有边都删掉。那我们就事先商量好一个随机 \(01\) 串,把每个点的策略异或一下,在交互库的视角里面,它只知道一个随机 \(01\) 串,如果 \(X\) 的每个二进制位用 \(80\) 个节点代表,正确率高达 \((1-2^{-80})^{60}\) 约为 \(0.99999999999999999999995036916325\),非常优秀。
😅。
$ \text{习题 1.3:}$
- 尝试用这种方法在 习题 1.1 的一道题 https://qoj.ac/contest/1782/problem/9237 中得到尽可能高的分数。
\(\text{2 交互题}\)
一般来说做交互题时,你需要先明确解决问题需要什么信息,针对这一点设计询问方式。
一些题目需要运用启发式算法(俗称乱搞),还有一些题目需要发现一些性质,或者“灵光一现”。
\(\text{2.1 随机化与启发式算法}\)
这部分的题目,解法大多为随机化算法或启发式算法。
对于复杂的随机化算法或启发式算法,只需要感性理解复杂度,不需要证明,多数情况下,不断加入你所认为正确的优化,优化着优化着,就过了。
\(\text{例题 2.1.1:网络恢复}\)
题意:
给定整数 \(N\) 和 \(M\),有一张 \(N\) 个点 \(M\) 条边的无向简单图,需要你猜测出所有的边。
你可以进行至多 \(50\) 次如下操作,每次操作步骤如下:
- 给每个节点 \(i\) 设定一个权值 \(a_i \in [0,2^{64}-1]\)。
- 选取一个边的子集 \(S\)。
- 对于每个 \(i\),交互库会计算出 \(b_i = \text{xor}_{u=1}^{n} [(i,u) \in S] a_u\),即,在只考虑 \(S\) 中的边的情况下,所有 \(i\) 的邻居的权值异或和。
- 你会得知所有的 \(b_i\)。
\(N = 10^4, M = 3 \times 10^5\),不自适应。
解法:
将所有边随机分成大小相等的 \(50\) 份,对每一份进行一次询问,\(a_i\) 随机生成,得到的 \(b_i\) 可以看做是对 \(i\) 的邻居集合进行 XOR-Hashing 的结果。
如果是一棵树的话,可以类似拓扑排序,逐步删叶子(叶子的判定只需要检查 \(b_i\) 是否为某个设定的权值即可)。这个想法可以拓展到图上,先逐步删度数为 \(1\) 的点,如果没有了,就随便取一个点 \(x\),有很大概率其度数为 \(2\),枚举一个点 \(y\),用哈希表查询是否存在另一个点 \(z\),如果有,就成功删掉了 \(x\),此时它的两个邻居 \(y,z\) 可能成为度数为 \(1\) 的点,加入队列后继续删即可。
\(\text{例题 2.1.2:Longest Trip}\)
题意:
给定一个 \(N\) 个点的无向图,保证对于任意三个不同的节点 \(u,v,w\),边 \((u,v),(v,w),(u,w)\) 中至少存在一条。
你可以进行若干次询问,每次询问给出两个点集的子集 \(A,B\) 满足其交集为空。交互库会告诉你是否存在 \(x,y\) 满足:\(x \in A,y \in B\) ,且边 \((x,y)\) 存在。
试通过询问求出一条最长路。
\(3 \le N \le 256\),询问不超过 \(400\) 次得满分,不自适应。
解法:
考虑充分利用“每三个点之间至少有一条边”的性质。
维护两条链 \(L_1,L_2\),令两条链的第一个节点为 \(s_1,s_2\),最后一个节点为 \(t_1,t_2\),步骤如下:
- 检查 \(t_1,t_2\) 之间是否有边,如果有就将 \(L_2\) 接在 \(L_1\) 后面,然后清空 \(L_2\)。
- 任取一点 \(u\),检查 \(u,t_1\) 之间是否有边 ,如果有就将 \(u\) 加入 \(L_1\) 末尾。如果没有,此时要么 \(L_2\) 为空,可以把 \(u\) 加到 \(L_2\) 的末尾;要么 \(u,t_1\) 没边,\(t_1,t_2\) 没边,\(u,t_2\) 一定有边,也可以把 \(u\) 加到 \(L_2\) 的末尾。
执行上述步骤,会得到两条链,查询 \((s_1,s_2),(s_1,t_2),(t_1,s_2),(t_1,t_2)\) 是否有边,如果其中之一有,找到哈密顿路,做完了。否则由于 \((s_1,s_2),(t_1,s_2)\) 没边,\((s_1,t_1)\) 有边;由于 \((s_2,s_1),(t_2,s_1)\) 没边,\((s_2,t_2)\) 有边,也就是我们会得到两个环。然后查一下两个环之间是否有边,如果没有,输出最大的环;如果有,二分找一下,然后在一个环绕一圈,经过这条边走到另一个环,然后再另一个环上在绕一圈,找到哈密顿路。
询问次数是 \(O(n)+2 \log n\) 的,得到 \(85\) 分,考虑给 \(O(n)\) 部分稍微改改。
维护两条链,可以设计势能函数为:两条链大小之和。检查 \(t_1,t_2\) 是否联通这一步并不会减少势能。
如果同时维护多条链,每次随机取两条链,检查末尾是否有边,可以设计势能函数为:链的个数。检查末尾是否有边这一操作,有概率减少势能。
还有一个优化是,有时会已知存在两条链的末尾之间没有边,此时不需要检查,直接拿来做即可。
\(\text{例题 2.1.3:麻将}\)
https://yundouxueyuan.com/p/YDRG005F?tid=65ccf4cbfbfb0fad8af18874
题意:
本题的麻将不含字牌,共 \(3 \times 4 \times 9=108\) 张,不允许七对子胡。
在本题中,你需要和交互库玩如下游戏:
- 游戏开始时,交互库将这一副麻将按某种顺序排列组成牌堆,保证牌堆顶 \(13\) 张牌的听牌集合大小 \(\ge 2\)。
- 交互库将牌堆顶 \(13\) 张牌作为她的手牌,然后将牌堆顶第 \(14\) 张到第 \(43\) 张牌(共 \(30\) 张牌)亮出,你可以看到这些牌,注意这些牌可能属于交互库手牌的听牌集合,也可能不属于。然后移除这 \(43\) 张牌(即此时牌堆顶的牌,是初始牌堆的牌堆顶第 \(44\) 张牌)。
- 你猜测一张牌,若这张牌属于交互库手牌的听牌集合,则游戏结束;否则交互库会亮出牌堆顶的 \(3\) 张牌中不属于听牌集合的所有牌,并移除牌堆顶的 \(3\) 张牌,你需要继续猜测。注意,如果听牌集合内的一张牌全部被翻出,其仍然属于听牌集合。
- 游戏结束时,令你的代价为你的猜测次数。
你需要尽可能最小化游戏的代价。
保证数据随机,\(10000\) 局游戏猜测次数不超过 \(41301\) 次即可通过,更多细节见原题面。
解法:
这是一道考察纯启发式算法的交互题。
如果从 \(5\) 往两边猜,并跳过已经确定不合法的,可以得到 \(12\) 分上下。
本题中保证至少两面听,大致可分为如下几种听牌的类型:
- \(x,x+1\) 型,例如 1m 1m 1m 2m 2m 2m 3m 3m 3m 4m 4m 2s 3s。
- \(x,x+1,x+2,x+3\) 型,例如 1m 1m 1m 2m 2m 2m 3m 3m 3m 1s 2s 3s 4s。
- “双碰”型,例如 1m 1m 1m 2m 2m 2m 3m 3m 3m 4s 4s 5s 5s。
也可能会有 \(\ge 3\) 面听,事实上这种情况出现的较少,对代价的贡献也比较小,我们可以忽略。
介绍一下麻将的防守技巧:
- 筋牌:假设你知道对面不听某花色的 \(4\),那么听 \(1\) 的概率会大大降低(不存在 \(2,3\) 或 \(1,2,3,4\) 型),听 \(7\) 的概率会有所降低。
- 壁牌:假设牌河中已经有很多某花色的 \(3\),那么听 \(1,2\) 的概率会大大降低;若牌河中有很多 \(4,6\),那么听 \(5\) 的概率会大大降低。
上述技巧不能适用于“双碰”型,即 \(3\) 个面子 \(2\) 个雀头的听牌形状。事实上这类听牌在现实生活中,在不知道对方是这类听牌的情况下也没有很好的防守技巧。
我们需要对筋和壁设计估价函数,如果对这两种防守技巧设定参数,会变得很复杂,尤其是壁牌。但这两种技巧有一个共性,即它们都减少了在剩余牌中凑出听牌形状的方案数。
我们令 \(f(x)\) 表示牌 \(x\) 的估价函数,对于 \(x,x+1\) 型是很好设计的,把没有筋牌的部分两张牌的剩余数量乘起来就行;对于 \(x,x+1,x+2,x+3\) 型,由于要乘四张牌,显然不平衡,根据物理学中的单位统一,把这四张牌乘起来开根;“双碰型”的话,它还是需要乘四张牌后开根,而且这种类型比较特殊,它的出现概率会随着你猜测次数增大而增大,所以还需要乘上一个随操作次数递增的函数,可能要调一会儿这个参数。
这个函数的表现并不是很理想,我们令 \(g(x)\) 表示,若钦定 \(x\) 不合法后,所有牌的 \(f(x)\) 之和,然后按选取 \(g(x)\) 最小的即可。
欢迎各路大神薄纱 std!
\(\text{习题 2.1}\)
- https://qoj.ac/problem/9909
- https://qoj.ac/problem/9914
- https://qoj.ac/problem/9156
- https://qoj.ac/problem/5568
- https://uoj.ac/problem/810
\(\text{2.2 交互题中的思维题}\)
这一节中的题目,往往需要多发现性质,或者需要“灵光一现”。
\(\text{例题 2.2.1:Ancient Machine 2}\)
https://qoj.ac/contest/1300/problem/6774
题意:
你需要猜测一个长度为 \(N=1000\) 的 \(01\) 串 \(S\),一次询问需要给出一个整数 \(m \in [1,1002]\) 和两个长为 \(m\) 的序列 \(a,b\),序列中的每个元素 $ \in [0,m-1]$。
交互库会做如下的过程,初始时令 \(u:=0\),然后进行 \(N\) 次操作,第 \(i\) 次操作为:若 \(S_i=0\),则令 \(u := a_u\);若 \(S_i = 1\),则令 \(u:=b_u\),其中 \(:=\) 表示赋值操作。
操作完后会告诉你 \(u\) 的值。
也就是说,你需要给出一个大小为 \(m\) 的自动机,交互库会返回跑完这个字符串 \(S\) 后 \(u\) 的值。
你至多进行 \(1000\) 次询问,欲获得满分,你需要保证 \(m \le 102\)。
解法:
令 \(n=1000\),即答案长度。
由于不需要优化询问次数,且询问次数限制为 \(n\),考虑问出 \(n\) 个方程把答案解出来。
一个想法是选一些位置,问出它们的异或和。询问的自动机大概是有左右两部分,读取到这些位置的时候,如果这个位置为 \(1\),就跳到另一部分,最后根据落在哪个部分得到某些位置 \(1\) 的个数奇偶性,也就是这些位置的异或和。
搞两个长度为 \(x\) 的环,对于每个环上的第 \(y\) 个节点,\(1\) 边指向另一个环的下一个节点,其余边均指向当前环的下一个节点,询问这个自动机,可以得到所有模 \(x\) 为 \(y\) 的位置上的异或和,只需要选 \(n\) 个二元组 \((x,y)\) 就行。
不幸的是,这个矩阵不满秩,如果想要满秩的话,\(x\) 的最大值能达到 \(54\),即 \(M=108\),不能通过。
考虑先确定前 \(100\) 位,这个是简单的,确定第 \(i\) 位时,节点 \(j(j \lt i)\) 两条边指向 \(j+1\),节点 \(i\),\(0\) 边指向 \(i+1\),\(1\) 边指向 \(i+2\),\(i+1,i+2\) 的两条边均指向自己。
然后考虑确定后 \(100\) 位,确定倒数第 \(i\) 位时,询问 \(0+(倒数第 i-1 位开始的后缀)\) 的 KMP 自动机,看是否在最后一位上匹配。如果匹配则第 \(i\) 位是 \(0\),否则是 \(1\)。
对于中间 \(800\) 位解方程,正好就卡进去了。
用 bitset 做消元,时间复杂度 \(O(n^3/w)\)。
\(\text{习题 2.2}\)
\(\text{3 提交答案题}\)
常用的方法有:iddfs,A*,idA*,模拟退火,爬山,随机调整,估价函数。
但这不意味着只要了解了这些算法,你就是提答高手了。对什么设计 A* 的估价函数,怎样剪枝,对什么退火,如何随机,怎样实现更高效,都是要慢慢摸索的。
\(\text{3.1 搜索类提答}\)
此类题目的关键是找一个好的估价函数。
好的估价函数往往需要一定的时间尝试,“提答花的时间越久分数越多”这句话的原因就在此。
\(\text{例题 3.1.1:}N^2\text{ 数码游戏}\)
题意:
用尽可能少的步数求解数字华容道。
题解:
对于测试点 \(1\),可以爆搜,复杂度 \(O((N^2)!poly(N))\)。
对于测试点 \(2,6\),可以剪十六个小纸片玩,观察发现前若干次操作一定是转矩形的最外面一圈,类似于 UUULLLDDDRRRUUULLLDDDRRR... 的过程,当把 \(1,2,3,4\) 转到最上面的时候,剩余部分可以暴力或者用下文方法解。
考虑 A*,设计一个估价函数,每一层只保留一些可能比较优的状态,可以试试如下几个。
- 每个数到终点的曼哈顿距离,的和/平方和/立方和/平方根和。
- 重新定义距离:\((x_1,y_1),(x_2,y_2)\) 的距离为 \((|x_2-x_1|+1)(|y_2-y_1|+1)\),算和/平方和/立方和/平方根和。
- 将距离乘上 \(0\) 到这个数的距离,算和/平方和/立方和/平方根和。
实测和/平方根和比较优秀。
这样子会出现一个现象,目前队列中估价函数最低的会在两个数中反复横跳,每次扰动一步显然是不够的。可以在模 \(2/3/4=0\) 的某一轮中删减状态。
需要调一调删除的频率和保留的状态数。
当然估价函数并不全面,比如有两个相邻但位置相反的数,空格又隔得特别远。其实这种状态是很劣的,但这几种估价都会认为这种状态很优秀。或者说多个路径交错在一起,也是特别劣的,但很难设计函数把它们区分开,欢迎各位来交流更好的估价函数。
题外话:关于多项式复杂度的构造方法,也是我的赛时做法:
- 大概思路是按照 \(1,2,\cdots,N^2\) 的顺序归位,已经归位的不去动它。
- 对于前 \(N-2\) 行的前 \(N-2\) 列,记录状态 \((ux,uy,ex,ey)\),表示目前需要归位的数和空格分别在什么位置,爆搜就行。
- 对于前 \(N-2\) 行每一行的最后两列,并不能保证上一条能跑出解,但是我们一定可以把棋盘变成下面的形状之一(.表示空格,?表示没被归位的数字):
1 2 . 3
? ? ? 4
1 2 4 .
? ? 3 ?
所以记录状态时记录两个数的位置和空格的位置,也一定能得到解。
- 对于后两行,由于最后一行极有可能顺序不对,上述方法不能使用。从左往右枚举列,同时归位这一列的两个数,假设 \(N=4\),目前归位 \(9,13\),只需要在第三行令 \(13\) 和 \(9\) 挨在一起,然后转下来就行,也可以记录三个坐标爆搜。
1 2 3 4
5 6 7 8
? 13 9 ?
? ? ? .
复杂度 \(O(n^7)\)。肯定有更优的做法。
但这样构造,我只在测试点 \(9\) 得到了 \(3\) 分,其余均为 \(2\) 分。
\(\text{习题 3.1}\)
\(\text{3.2 随机类提答}\)
与 \(3.1\) 同理,需要尝试退火的对象,尝试不同的参数。
要注意的是,一般来说,跑一轮退火容易陷入局部最优解,跑多轮退火就能解决这一点。
\(\text{例题 3.2.1:Road Service}\)
题意:
给一棵无向无权树,你需要往里面加入 \(K\) 条边,尽可能最小化:两两节点之间的最短路长度之和。
解法:
说一下我做这个题的过程:
我一开始想的是,加入一条令总距离减少最多的边,但这样实在是太慢了,于是我就随了几条边取最优,这个表现并不是很优秀,在 case 2 只得到了 \(18\%\) 的分数。
打算给 case 1 写一个暴力,算量是 \(\binom{n(n-1)/2}{k}n^2\),在一个不错的电脑上能 \(10\) 分钟内出解,发现连出来的是一个菊花图,想了想发现菊花图确实很对,因为菊花图内部的路径非常短,每个点到菊花图上的路径长度也很小。
直觉告诉我,菊花的根选择重心,但是选择菊花叶子比较麻烦,因为算一次代价是 \(O(n^2)\) 的,但是可以随机尝试几个叶子啊,这样能在几分钟内在 case 2 跑出 \(60\%\) 到 \(70\%\),调用全家电脑去跑枚举所有叶子的程序,得到了所有点的 \(80\%\) 到 \(90\%\)。
题目中的代价不好维护,我想了一个估价函数,我们不妨忽略菊花,直接把菊花中的所有点“缩起来”。由此想到估价函数:每个点到菊花根的最短路径之和,这样计算一次代价只需要 \(O(n)\) 的时间。
先贪心选令估价函数减少最多的点,选出一个集合,枚举集合内哪个作为根,可以在 \(O(kn^2)\) 的时间内运行完毕,而且非常优秀,能在所有点得到 \(90\%\) 到 \(95\%\)。
对叶子退火试试?每次扰动选择一个叶子和菊花图外的点交换并估价,每个点跑五六分钟就可以得到满分。
此题还有别的做法,一种是对叶子爬山,每次扰动随机一个子集换出,这个子集大小需要调一调,你甚至可以动态调整,发现爬不动了就多换一点。
还有一种是最小化估价函数的确定性做法,类似 NOI2008 奥运物流的二维树上背包,实现的好可以在一秒内出解,很厉害。
我的退火代码在 i7-1260P 2.10GHz 16GB 上,能在一分钟的时间对 case 6 处理完毕。
\(\text{习题 3.2}\)
\(\text{碎碎念}\)
熟悉笔者的都知道,笔者是非传统题和启发式算法的究极爱好者,热衷于用各种“歪解”过题。
比如 2023 NOIP t3 ,我在长时间的坐牢之后,突发奇想用特殊性质的代码去跑没有特殊性质的大样例,发现只筛掉了约一半的测试点,由此尝试把序列 reverse 一下再跑一遍,就通过了;在 2024 联合省选 d2t2 中,对贝尔数的搜索加了很多剪枝,多跑过了 \(15\) 分。
到了 NOI 2024,我非常幸运的在 day1 遇到两道擅长的题目:t1 是本文开头提到的 xor-hashing,t2 是我擅长的交互题,这两道题都是飞速通过,给 t3 留下了充足的时间;在 d2t1 中,根据多年写搜索的经验,想到去优化搜索的最后一层递归,多获得了 \(10\) 分,这让我即便在误判 d2t2 难度,丢掉 \(40\) 分的情况下,凭借这 d2t1 多的 \(10\) 分和 day1 的优势,擦线进入了集训队。
进队之后,我被邀请到一些学校讲课交流,在正式内容提前讲完的情况下,我都会用之前做的非传统题填满剩下的时间。如果一切顺利,这篇文章会是我的候选队论文,可惜我在 CTT 中惨烈坠机,准确的说,根本就没起飞,没有任何一场比赛进入过前 \(30\)。
每当有人和我提起“乱搞无用论”之时,我都会用我的 oi 生涯坚定的反驳他,如果我不会乱搞,这个集训队我还能进吗,甚至说,这个江苏省队我还能进吗?
希望你们都爱上乱搞,爱上非传统题!

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号