非传统题做题记录
非传统题做题记录
主要是构造、提答、交互、通信之类的。
CF1207E XOR Guessing
值域只有 \(2^{14}\),一次必须输出 \(100\) 个数,只能问两次?
这限制的太死了,容易想到 \(2^{\frac{14}{2}}=128\approx 100\),直接想到一次询问确定一半的二进制位,做完了。
再放一道类似的送福利题 CF1722G Even-Odd XOR
显然条件等价与所有数的异或和为 \(0\),\(n \ge 3\)?显然我可以构造 \(2^29,2^30,2^29+2^30+X,X<2^29\),三个数异或和为 \(X\),其余 \(n-3\) 个数直接取 \(1\cdots n-3\) 即可。
CF1270G Subset with Zero Sum
值域与 \(i\) 有关,不妨令 \(b_i = i-a_i\),则 \(1 \le b_i \le n\)。
原来要求 \(\sum _{i \in S} a_i =0\),即 \(\sum _{i \in S} b_i - i =0\),变形可得
一个想法是让 \(b_i\) 与 \(i\) 一一对应。所以我们考虑建图,连边 \(i \to b_i\),在图中的环显然满足题意。而这是一个基环树(森林),显然至少有一个环。
CF1746E2 Joking (Hard Version)
最优化交互。
首先有一个 \(2\log_{\frac{3}{4}}(n)+o(1)\) 次交互的做法。加入现在剩余的集合为 \(S\),我们可以将其划分为 \(A,B,C,D\) 四个不交的子集,然后依次询问 \(A \cup B\) 和 \(A \cup C\),这样可以排除一个集合(具体如下表,行表示第一次回答的结果,列表示第二次回答的结果,表中的值表示排除掉的集合。)。可以通过 Easy Version。
| T | F | |
|---|---|---|
| T | \(D\) | \(B\) |
| F | \(C\) | \(A\) |
考虑上面为什么做的不够优。原因就是两次询问信息直接信息复用没做好。
显然询问至只与集合大小有关,与集合中具体的数值无关。所以我们考虑复用信息。设上一次询问的集合为 \(T\),没有询问的集合为 \(F = U \setminus T\),将 \(T\) 划分为 \(A,B\) 两个不交集合,将 \(F\) 划分为 \(C,D\) 两个不交集合,下一步查询 \(A \cup C\),也可以至少丢到掉一个集合。
为了最优化交互次数,设 \(f_{|T|,|F|}\) 表示最劣情况下的询问次数,则边界情况为 \(|T|+|F| \le 2\),此时直接猜即可,询问次数为 \(0\)。
枚举将 \(x=|A|\) 和 \(y=|C|\),可以得到如下转移:
然而这个 dp 是 \(O(n^4)\) 的,只能做 \(|T|+|F| \le 100\) 的情况。于是在 \(|T|+|F| \lt 100\) 时我们可以使用类似较劣的做法,将 \(T\) 和 \(F\) 均分询问。
初始为 \(T = \{1,2,\cdots,n\},F=\varnothing\)。
如上法交互,可以在恰好 \(53\) 次得到答案。
P10786 [NOI2024] 百万富翁
subtask 1 是极其平凡的。直接 \(\binom n 2\) 询问所有点对的关系,然后将其作为比较符排序即可。
然后考虑 subtask 2。\(T=20\) 意味着要想得分至多请求 \(\log _2 n\) 次。这样就得到一个可以在 subtask 2 获得 11pts 的做法:每次将两个人绑为一组,然后询问那个更大,只保留最大的那一个。最后询问次数为 \(\frac{n}{2}+ \frac{n}{4}+\cdots = 10^6-1\)。
现在的问题是 \(t\) 大了,\(s\) 小了;而 subtask 1 的问题是 \(t\) 小了,\(s\) 大了。考虑综合一下。得到一个这样的做法:
每次将 \(k\) 个绑为一组,总共有 \(\left \lceil \frac{n}{k} \right \rceil\) 组,每组内 \(\binom k 2\) 求最大值,保留最大值,进入下一轮。则剩余 \(n\) 个人分 \(k\) 组单次询问数为 \(\left \lfloor \frac{n}{k} \right \rfloor \frac{k(k-1)}{2}+ \frac{r(r-1)}{2},(r=n \bmod k)\),写一个简单的程序可以知道 \(k\) 依次取 \([2, 2, 2, 2, 3, 6, 19, 183]\) 时最优,总方案数为 \(1099947\),之差一点了。
考虑上面哪里做的不好,由于最后的散块比较小,不平衡,将其平衡到前面的块中即可。
具体地,令 \(t\) 为本步后还剩的人数,则模拟退火等办法可以得出 \(t\) 取 \([500000, 250000, 125000, 62496, 20832, 3472, 183, 1]\) 时最优,正好 \(1099944\) 次。
P13535 [IOI 2025] 纪念品(souvenirs)
终于不限制交互次数了!(每次至少买一个物品,最多买 \(0 + 1 + 2 + \cdots +N-1 =4950 < 5000\) 次,怎么也不可能超次数)
先来尝试模拟一下前几步。根据数学直觉,第一步应该购买 \(P_0 -1\)(大了会买 \(0\) 号物品)。然后我一定买了 \(1\) 号物品,此后就不能买 \(1\) 号物品了。假设我直接购买了 \(S\),我就知道了 \(\sum \limits_{x \in S} P_x\) 的值。
然后如果 \(S = \{ 1 \}\),这就是一个非常好的结果,我就知道了 \(P_1\),接下来问 \(P_1-1\) 即可。否则我可以购买 \(\frac{\sum _{x\in S} P_x}{|S|}\),这样一定可以避免购买 \(1\) 号物品,同时也不会因为过小而一个没买。(这样可以通过 \(n=3\))
这样就可以实现将从 \(\min S\) 逐渐推到 \(n-1\)。得到了 \(P_{n-1}\)。然后考虑倒推。在所有包含最后已知的后缀的询问全部可以直接减掉即可,转化成一个更小的子问题。这样我们就可以得到所有物品的价格了。而每个物品只可能在 \(\min S \le i\) 购买,切 \(\min S\) 是单调递增的,故每个物品至多购买 \(i\) 次,最后再补上剩余的即可。
P13614 [IOI 2018] highway 高速公路收费
属于交互内容很抽象的一类题。所以我们需要将要求限制的严格一点,更容易得到信息。
容易想到先查询一遍所有边权均为 \(A\) 的情况,这样我们就有了一个基准。如果以后查询到的最短路大于基准,那么说明 \(S \to T\) 的最短路至少有一条边为 \(B\),则称 \(S\) 和 \(T\) 断开了。断开某条边指将这条边的权值设为 \(B\)。
现在问题棘手的一点是 \(S\) 和 \(T\) 是强相关的,不好处理。所以我们可以考虑将 \(S\) 和 \(T\) 断开。
具体地,我们可以二分一个边的前缀,当下标在 \([1,h)\) 中的边断开时,对 \(S\to T\) 的最短路无影响,而断开下标在 \([1,h]\) 中的边会断开,那么下标为 \([1,h)\) 中的边对于我们求解问题来说没有意义,直接丢掉,得到另一张图 \(G ^\prime\)。
然后我们知道在 \(G^\prime\) 存在一条边 \(u \to v\),断开这条边后 \(S \to T\) 就断开了。这样我们就可以将所有结点按距离 \(u\) 和 \(v\) 的距离远近分为两部分,令距离 \(u\) 近的结点集合为 \(U\),距离 \(v\) 近的结点集合为 \(V\)。下证 \(S,T\) 一定不同时属于 \(U\) 或 \(V\)。(距离相等的点放入 \(U\) 或 \(V\) 均可,下面证明可以清楚的体现这一点)
首先证明若点 \(x\) 在 \(G^\prime\) 上满足 \(d_{x,u}=d_{x,v}\),则 \(x\) 一定不可能为 \(S\) 或 \(T\)。不妨假设 \(S=x,T\in U\),显然 \(x \to u\) 和 \(x \to v\) 的路径不经过 \(u \to v\),则 \(T \to u \to x\) 的路径显然短于任意一条经过 \(u \to v\) 的路径。即不可能经过 \(u \to v\) 这条边为 \(S \to T\) 的最短路。接着,\(S\in U,T \in U\) 也是不可能的,否则 \(S \to u \to T\) 也短于经过 \(u \to v\) 的路径。
然后我们希望在 \(U\) 和 \(V\) 中找到 \(S\) 和 \(T\) 两个点。如果我们有一个有 \(U\) 中元素组成序列,满足依次加入元素图任然联系,就可以二分一个前缀,将不在这个前缀的点的连边全部断开,就可以二分出 \(S\) 的位置。
交互次数:记两边最后分为的两部分大小分别为 \(B,N-B\)(实际上由于我们之前删了边,可能总和没有 \(N\),但此处我们只讨论最劣情况),则交互次数为 \(1 + \log M + \log B + \log (N-B)\) 。所有的 \(\log\) 都要向上取整。
具体的有以下代码跑出来的最多次数为 \(50\)。(也可以理论分析,只是有向上取整不大好搞)
from math import *
N=int(9e4)
M=int(1.3e5)
ans=0
for i in range(1,N):
ans=max(ans,int(ceil(log2(i))+ceil(log2(N-i))))
ans+=1+ceil(log2(M))
print(ans)
闲话:关于本题交互库的实现。本题下发的 grader.cpp(qoj 有)中采用了一个比较巧妙的最短路,节选如下:
grader.cpp节选
std::vector<int> w;
std::vector<std::vector<std::pair<int, int>>> graph;//出边,边的下标
std::vector<bool> visited(N, false);
std::vector<long long> current_dist(N, INF);
std::queue<int> qa, qb;
qa.push(S);
current_dist[S] = 0;
while (!qa.empty() || !qb.empty()) {
int v;
if (qb.empty() ||
(!qa.empty() && current_dist[qa.front()] <= current_dist[qb.front()])) {
v = qa.front();
qa.pop();
} else {
v = qb.front();
qb.pop();
}
if (visited[v]) {
continue;
}
visited[v] = true;
long long d = current_dist[v];
if (v == T) {
return d;
}
for (auto e : graph[v]) {
int vv = e.first;
int ei = e.second;
if (!visited[vv]) {
if (w[ei] == 0) {
if (current_dist[vv] > d + A) {
current_dist[vv] = d + A;
qa.push(vv);
}
} else {
if (current_dist[vv] > d + B) {
current_dist[vv] = d + B;
qb.push(vv);
}
}
}
}
}
可以看到其中并没有使用优先队列,而是用了两队列。一个结点被第二次松弛只有可能是上一次松弛的边权为 \(B\),下一次为 \(A\),所以维护两个队列,qa 存上一次由权值为 \(A\) 松弛的结点,qb 存由 \(B\) 松弛的结点,显然两个内部可以保证距离单调递增,拼起来就是优先队列。
AT_agc044_d [AGC044D] Guess the Password
AIGC 使用说明
由于我自己写的 C++ 代码有些抽象,但合并的过程不结合代码不好说明,所以我使用 AI 将其转化为伪代码,并经行人工核验、添加注释。
又是一道交互内容很抽象的题。编辑距离似乎只在某道黄题中见过?
记 \(\Sigma\) 为字符集,\(|\Sigma|=62\),\(L = |S|\)。
首先我们考虑到我们不知道 \(S\) 有多长,有哪些些字符组成。所以我们依次问长为 \(128\) 全 \(A\) 串,\(A \in \Sigma\),得到 \(A\) 在 \(S\) 中出现的次数。具体地,假设的到的编辑距离为 \(D\),那么 \(A\) 的出现次数就为 \(128 - D\)(超过 \(L\) 的 \(A\) 可以直接删掉,\(S\) 中其它部分非 \(A\) 的会直接改为应该的值)。所有的字符出现次数之和,就得到了 \(L\)。
然后我们注意到,如果查询 \(T\) 得到的 \(D = L - |S|\),那么说明 \(T\) 为 \(S\) 的一个子序列。于是我们可以在 \(n+m\) 次询问合并两个已经为 \(S\) 的子序列的串 \(A,B\),得到一个新的串 \(C\) 任然为 \(S\) 的子序列。具体地,依次尝试在 \(A\) 的某个位置后加入 \(B\) 的字符,检验其是否为 \(S\) 的子序列,可以写出这样的(伪)代码。
然后我们相当于初始有了 \(|\Sigma|\) 个字符串,总长为 \(L\),需要将其合并,合并代价为两个字符串长度之和。最小化代价,这是一个经典的问题,合并果子。使用优先队列即可。可以证明,总操作次数为 \(\mathcal O ( L \log |\Sigma| + |\Sigma|)\),可以通过。
最后放一下我的抽象代码。
QOJ #14942. 出 Bug 的绘画软件 II/ Buggy Painting Software II
见这里。

浙公网安备 33010602011771号