1 条题解
-
0
自动搬运
来自洛谷,原作者为

Jason331
这个家伙很菜,什么也没有留下搬运于
2025-08-24 22:59:54,当前版本为作者最后更新于2024-08-29 19:33:13,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
因为本篇文章篇幅较长,博客食用更佳。
前言
解题方法 and 前置芝士
-
二分
-
倍增
-
分类讨论加模拟
请求添加标签
请求为本题添加
二分与倍增标签,理由见本文。虽然我不确定其他的方法是否要用到倍增,但是二分在本题中是绝对要用到的。
我与此题的关联
此题一大半的提交都被我拿下了,不写个题解都说不过去了……

对本题的评价
非常
毒瘤好的一道既能烧脑训练思维又能把人逼疯锻炼码力的题目,能够全方位地摧残提升做题的 oier,非常适合推荐给与你勾心斗角忠心耿耿的朋友去做。基础解题思路
前置定义
因为本文较长,特作如下定义:
-
将“第 个夸克”简称为“夸克 ”()。对于任意自然数 ,夸克 与夸克 之间不存在任何夸克,这个理由在下文中可能不会具体说明。
-
表示夸克 的位置()。
-
表示与位置 相距第二近的夸克有多少个($-10^{18} \leq x \leq 2 \times 10^{18},1 \leq opt_x \leq 2$,有关“相距第二近的夸克”的定义请看题面)。
-
表示与位置 相距第二近的夸克的距离($-10^{18} \leq x \leq 2 \times 10^{18},1 \leq l_x \leq 10^{18} - 1$,有关“相距第二近的夸克”的定义请看题面)。
-
将“询问与位置 相距第二近的夸克的距离与数量” 简称为“询问 ”()。
-
将“存储夸克 的位置(为 )”简称为“标记 (为 )”()(出现这个短语时如果没有说清标记位置,说明对于位置的推理极为简单,请联系上下文自行实现)。
开始的几个夸克
仔细阅读题面,我们可以推断出:
询问 ,可得:
然后询问 ,得到的一定是夸克 和夸克 中距离 较近的一个夸克的距离(因为离位置 最近的一定是夸克 ,距离为 ),此时如果 ,就可以直接标记 和 ,然后跳过此部分,直达 接下来——一个一个地找夸克。
但是如果 ,如何确定 是夸克 与夸克 的距离还是夸克 与夸克 的距离呢?
询问 。
分类讨论
-
,直接标记 。
-
,标记 为 ,原因略。
-
,标记 为 ,原因略。
如果上面三种情况都没有出现,那么可以标记 为 ,理由请自行证明。
然后,我们就可以二分了。
通过上面的操作,我们可以确定 ()。
对于任意位置 (),如果:
$$k_2 < x + l_x < k_3 \small \bigvee \normalsize opt_{x} = 2 $$温馨提示: 是逻辑或符号。
那么:
原因:
因为 ,可知夸克 是距离 最近的一个夸克,又因为 ,所以距离 第二近的夸克肯定不是夸克 ,所以 。
关于 时的证明请读者自行补证。
接下来,我们的问题就转化为了找到一个位置 ,使得上述式子成立。
开始正题——二分。
二分前记住我们的目的:
-
夸克 成为距离询问位置第一近的夸克。
-
夸克 成为距离询问位置第二近的夸克。
-
夸克 成为距离询问位置第三近的夸克。
右端点 (将变量 赋值为 ),左端点 。
取 (其实向下取整和向上取整都可以,我这里只是示范)。
单次循环的步骤
询问 。
分类讨论:
-
,标记 ,退出循环,理由见上文。
-
,说明夸克 成为了第二近的夸克,我们要离夸克 再近一点:。
-
,说明夸克 成为了第二近的夸克,我们要离夸克 再远一点:。
如果上面三种情况都没有出现,那么标记 ,退出循环,理由见上文。
接下来——一个接一个地找夸克
进行定义
夸克 是我们正在找的夸克(,因为我们在上一部分已经找到了 或 个夸克,所以 )。
询问 。
如果 $k_{a - 1} - l_{k_{a - 1}} > k_{a - 2} \small \bigvee \normalsize opt_{k_{a - 1}} = 2$,那么标记 为
原因:对于位置 ,夸克 距此位置最近(距离为 ),如果夸克 不是距此位置唯一的第二近的夸克,那么夸克 就是距此位置第二近的夸克。
倍增
设变量 。
通过上面的操作,我们可以推断:在 中,没有夸克(请自行补证)。
进行循环:
询问 。
分类讨论
-
,再次分类讨论:
-
,跳出循环。
-
,标记 为 ,结束寻找。
此时夸克 是距离位置 最近的夸克,又因为 ,所以夸克 必然是距离位置 第二近的夸克。
-
-
,跳出循环。
-
,标记 为 ,结束寻找。
此时夸克 是距离位置 最近的夸克,而夸克 不是距离位置 第二近的夸克,所以夸克 为 。
-
,跳出循环。
如果以上四种情况都没有发生,那么我们可以断定 $x - l_x = k_{a - 2} \small \bigwedge \normalsize opt_x = 1$,因为其他情况已经全部讨论过了。
温馨提示: 是逻辑与符号。
因为 $x - l_x = k_{a - 2} \small \bigwedge \normalsize opt_x = 1$,所以夸克 是距离位置 第二近的夸克,夸克 是距离位置 最近的夸克,所以我们可以确定,在 中,没有任何夸克。
然后,。
继续循环。
补:我们在推导中的一个重要依据是:循环中的任意时刻,在 中,没有任何夸克。
继续寻找
我们根据循环跳出后的情形,再次分类讨论:
-
$x - l_x = k_{a - 1} \small \bigwedge \normalsize opt_x = 2$
由于题目的规定,此时有两种可能:
- $k_{a + 1} = x + l_x,k_a \in \lbrack x + 1,x + l_x - 1 \rbrack$
所以我们要确定在 中是否还有一个夸克。
如何确定呢?询问 。
分类讨论:
-
,标记 为 ,结束寻找。
如果在 中还有一个夸克,那此时距离位置 第二近的夸克一定是位于 的夸克(),所以 。
-
,标记 为 ,标记 为 ,结束寻找。
因为有可能 ,此时 。
如果两种情况都没有出现,那么说明在 中还有一个夸克。
标记 为 。
二分的方法与“3. ” 中一模一样。
-
$x - l_x = k_{a - 1} \small \bigwedge \normalsize opt_x = 1$
因为 $x - l_x = k_{a - 1} \small \bigwedge \normalsize opt_x = 1$,所以距离位置 第二近的夸克是夸克 ,又因为在 中,没有任何夸克。所以在 中有且只有一个夸克。
继续二分。
参考开始的几个夸克中的二分,我们需要找到一个位置 ,使得:
$$k_{a - 2} < y - l_y < k_{a - 1} \small \bigvee \normalsize opt_{y} = 2 $$那么就可以标记 为 。
证明就不写了,省略一大段,直接开始二分。
右端点 ,左端点 。取 。
询问 。
分类讨论:
-
,标记 ,退出循环。
-
,我们要离夸克 再近一点:。
-
,我们要离夸克 再远一点:。
如果上面三种情况都没有出现,那么标记 ,退出循环。
-
-
标记 为 ,因为此时夸克 至少是第三远的夸克,而在 中,没有任何夸克,所以夸克 在 中。
再次使用二分。
参考开始的几个夸克中的二分,我们需要找到一个位置 ,使得:
$$k_{a - 2} < y - l_y < k_{a - 1} \small \bigvee \normalsize opt_{y} = 2 $$证明就不写了,省略一大段,直接开始二分。
右端点 ,左端点 。取 。
询问 。
分类讨论:
-
,分类讨论:
-
标记 ,退出循环。
如果 ,此时位置 就只能是夸克 了。
-
其他情况,说明夸克 是第二近的夸克,要离夸克 再近一点:。
-
-
,我们要离夸克 再近一点:。
-
,我们要离夸克 再远一点:。
-
,我们要离夸克 再远一点:。
如果上面四种情况都没有出现,那么标记 ,退出循环,理由见上文。
-
以上就是本题的基本思路。
优化
以上的代码只能获得 60 分,我们需要再添加一些优化。
其一,使用 map 存储即可
观察代码,会发现有些位置可能会重复询问,所以可以使用 STL 中的 map(python 党用字典)来存储询问的信息。
其二
在一个一个地找夸克-继续寻找-3. 中,我们已经知道了夸克 的位置。
询问 。
如果 ,就可以直接标记 和 然后结束寻找了。
否则此时我们可以知道:
$$k_{a + 1} + l_{k_{a + 1}} = k_{a + 2} \small \bigvee \normalsize k_{a + 1} - l_{k_{a + 1}} = k_{a} $$原因有过说明。
所以此时我们可能直接得到 。
但是如何确定距离夸克 最近的夸克是夸克 还是夸克 呢?
用与开始的几个夸克中类似的办法。
询问 。
分类讨论
-
,直接标记 。
-
,标记 为 ,原因略。
-
,标记 为 ,原因略。
如果上面三种情况都没有出现,那么可以标记 为 ,理由请自行证明。
这样,就可能再次减少询问次数。
你还可以把这个方法用到一个一个地找夸克-继续寻找-1. $x - l_x = k_{a - 1} \small \bigwedge \normalsize opt_x = 2$ 中,因为这两种情况的都能在找到夸克 之前确定夸克 的位置。
其三,倍增倍数扩大
在一个一个地找夸克-倍增中,我们在单次循环结束后将变量 赋值为 。
我们不妨用两次倍增。
在第一次倍增开始时:
在第一次倍增的循环中:
如果 ,就说明在 中没有任何夸克,进行以下操作:
否则跳出循环。
用来记录变量 的历史版本。
第二次倍增开始时,。
再开始原来的倍增。
可以看出,这是在外面的倍增里面又套了一层倍增。
这样倍增中的询问次数就减少了,如果设两个夸克之间的距离为 ,原来倍增部分的复杂度是 ,现在的复杂度就是 $\lceil \log _{t + 1} j \rceil + \lceil \log _2 (t + 1) \rceil$。经过分析, 取 时次数最少,但是由于 long long 类型的储存上限是 ,所以 最多只能取到 (不想证明了),再大就有可能溢出了(当然你可以判断一下会不会移除再用更大的 )。
通过调整 的大小,最多询问次数的测试点可以达到 2250 次左右。
经过以上 3 个优化,就可以 AC 了。
代码
#include <bits/stdc++.h> using namespace std; struct rq { long long length; int ot; }; map <long long,rq> dict; long long n,t,kk[110]; long long get_mid(long long a,long long b) { return ((a - b ) >> 1) + b; } rq q(long long address) { map <long long,rq>::iterator it; it = dict.find(address); if(it != dict.end()) return it->second; rq r; cout << "? " << address << endl; cout.flush(); cin >> r.length >> r.ot; dict[address] = r; return r; } void put(int num) { cout << "! "; for(int i = 0;i < num;i++) cout << kk[i] << " "; cout.flush(); } void start() { rq reply,reply2,reply3; reply = q(0); kk[1] = reply.length; reply = q(kk[1]); if(reply.ot == 2) { kk[0] = kk[1] - reply.length; kk[2] = kk[1] + reply.length; return; } reply2 = q(kk[1] - 1); if(reply2.ot == 2) { kk[0] = kk[1] - 1 - reply2.length; return; } if(reply2.length == reply.length - 1) { kk[0] = kk[1] - reply.length; return; } if(reply2.length == 1 && reply.length == 1) { kk[0] = kk[1] - 1; return; } kk[2] = kk[1] + reply.length; long long l = 1,r = kk[1],q_mid; while(true) { q_mid = get_mid(l,r); reply3 = q(q_mid); if(reply3.ot == 2) { kk[0] = q_mid - reply3.length; return; } if(q_mid + reply3.length == kk[1]) l = q_mid + 1; else if(q_mid + reply3.length == kk[2]) r = q_mid - 1; else { kk[0] = q_mid - reply3.length; return; } } } void f1_m1(int address,rq l_reply,long long ge) { rq reply,reply2; long long l = kk[address - 1] + 1,r = ge - 1,q_mid; while(true) { q_mid = get_mid(l,r); reply2 = q(q_mid); if(reply2.ot == 2) { kk[address] = q_mid + reply2.length; return; } if(q_mid - reply2.length == kk[address - 2]) l = q_mid + 1; else if(q_mid - reply2.length == kk[address - 1]) r = q_mid - 1; else { kk[address] = q_mid + reply2.length; return; } } } void f1_m2(int &address,rq l_reply,long long ge) { kk[address + 1] = ge + l_reply.length; long long l = kk[address - 1],r = ge,q_mid; rq reply,reply2 = l_reply,reply3 = q(kk[address + 1]),reply4; if(reply3.ot == 2) { kk[address] = kk[address + 1] - reply3.length; kk[address + 2] = kk[address + 1] + reply3.length; address += 2; return; } reply4 = q(kk[address + 1] - 1); if(reply4.length == 1) { if(reply4.ot == 1) kk[address] = kk[address + 1] - 1; else kk[address] = kk[address + 1] - 2; return; } if(reply4.length == reply3.length - 1) { kk[address] = kk[address + 1] - reply3.length; return; } if(reply4.length == reply3.length) { kk[address] = kk[address + 1] - 1 - reply4.length; kk[address + 2] = kk[address + 1] + reply3.length; address += 2; return; } if(reply4.length == reply3.length + 1) kk[address + 2] = kk[address + 1] + reply3.length; while(true) { q_mid = get_mid(l,r); reply2 = q(q_mid); if(reply2.ot == 2) { if(q_mid + reply2.length != kk[address + 1]) { kk[address] = q_mid + reply2.length; return; } r = q_mid - 1; } if(q_mid - reply2.length == kk[address - 2]) l = q_mid + 1; else if(q_mid - reply2.length == kk[address - 1]) r = q_mid - 1; else { if(q_mid + reply2.length != kk[address + 1]) { kk[address] = q_mid + reply2.length; return; } r = q_mid - 1; } } } void find_1(int address,rq l_reply,long long ge) { if(ge - l_reply.length == kk[address - 1]) f1_m1(address,l_reply,ge); else f1_m2(address,l_reply,ge); } void find_2(int address,rq l_reply,long long ge) { rq reply2 = l_reply,reply_a = q(ge + 1); if(ge + 1 - reply_a.length == kk[address - 1]) { kk[address] = ge + reply2.length; if(reply_a.ot == 2) kk[address + 1] = ge + 1 + reply_a.length; return; } if(reply_a.length == reply2.length) { kk[address] = ge + reply2.length; kk[address + 1] = ge + 1 + reply_a.length; return; } f1_m2(address,l_reply,ge); } void find(int address) { if(kk[address]) return; rq reply = q(kk[address - 1]); if(kk[address - 1] - reply.length > kk[address - 2] || reply.ot == 2) { kk[address] = kk[address - 1] + reply.length; return; } long long ee = kk[address - 1] + reply.length + 1,e = ee; while(ee <= 1e18) { reply = q(ee); if(ee - reply.length == kk[address - 2]) { e = ee; if(reply.ot == 2) { kk[address] = ee + reply.length; return; } ee += reply.length * 18; } else break; } reply = q(e); while(e - reply.length == kk[address - 2]) { e += reply.length; if(reply.ot == 2) { kk[address] = e; return; } reply = q(e); } if(e - reply.length < kk[address - 1]) { kk[address] = e + reply.length; return; } if(reply.ot == 2) find_2(address,reply,e); else find_1(address,reply,e); } int main() { cin >> n >> t; start(); for(int i = 0;i < n;i++) find(i); put(n); return 0; }后记
本题思路极其复杂(用本人的方法),能看到这里来,已经很不容易了,您能给我点个赞吗?
主要思路一共 3 个二分,2 个倍增,还有数不尽的分类讨论加模拟,而询问次数最多的测试点还是危险的卡在 2200 次以上,欢迎各位读者出一些毒瘤的 hack 来卡我的代码,我会积极优化代码,推出更加优秀的题解!
做此类题的关键技能
- 使用模块化的编程思想,让程序架构更加清晰,维护与查错更加方便。
- 仔细推敲每一个细节,反复寻找被漏掉的可能情况。
- 巨大的耐心。
完结撒花!(呼!终于写完了……)
upd 2024.9.14: 修复了一些文法问题,增加了纯解法链接。
upd 2024.11.30:修复了一些文法问题,删了一些废话,删除了纯解法链接(没必要了)。
upd 2025.6.29:没有必要不公开代码,想抄就抄吧,只不过是自己心境的问题。
-
- 1
信息
- ID
- 10418
- 时间
- 1000ms
- 内存
- 1024MiB
- 难度
- 7
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者