【IOI 2018】Doll 机械娃娃
我感觉这个题作为Day2T1,有一定的挑战性。为$Rxd$没有完成这道题可惜。
我觉得这道题,如果按照前几个部分分的思路来想,就有可能绕进错误的思路中。因为比如说每个传感器最多只在序列中出现$2$次,很有可能会想到分别在每一个传感器之后用开关来控制。我在做这个题的时候就因为这个思路陷入僵局。事实上这个做法的弊端十分明显,首先如果可行的话,实现起来也会十分复杂,毕竟所有传感器序列中交错复杂,难于处理;其次,对于每一个传感器分别使用开关很可能导致开关的浪费。
这道题的两个关键的想法都源于其中的两个部分分,部分分的设置不仅是得分,往往对标算也有一定的启发作用。
把所有传感器的出口汇合在一起:
第$4$个$Subtask$中$n = 16$,这个$2$的幂次的数提示我们可以改变思路,把序列整体起来考虑可能更加简洁,更加清晰。
这么做会让题目简化,我们可以把那个汇集了所有传感器的开关向下建成一棵满二叉树(这里说明一下为什么每层都是满的,因为要保证每个开关都被经过了偶数次),底层$O(n)$个节点可以根据访问到的顺序确定它该连向那个传感器,或者连回开关的根。底层的节点数$L$应该是大于等于$\frac{n + 1}{2}$的最小的一个$2$的幂次。总的开关数就是$2L - 1$。在第$4$个$Subtask$中,我们按照这个方法只需要$15$个开关就能解决问题。
这里有一个小问题,就是如何知道某一个底层的出口会被第几次访问到。观察它的性质,发现它与$Fft$中的$rev$非常像,也就是从左到右第$i$个出口会被第$rev_{i}$次访问到。
于是看似这个算法很完美(事实上它已经十分优秀了,你可以光在后三个$Subtask$中得到$47$分),却有一点小瑕疵。当$n$是一个$2$的幂次多一点点的时候,$L$最坏会达到$n$个,也就是总节点数会被卡到$2n$个。这是一个很明显有很痛苦的瑕疵,所幸我们还有解决方法。
把冗余的开关剔除:
考虑到如果开关的某个出口没有被利用,我们之前的解决方法中,我们将它连向了开关的根,这是一次无用的滚动。我们唯一的问题就在于无用的滚动可能会过多。一个简单的想法就是如果某一个开关的两个出口都是连向开关的根的,那显然这个开关是无用的,我们可以把他直接去掉,把它父亲的出口连到开关的根去。如此做,直到没有什么可以删的点为止。注意,最后一次访问到的底层开关不能删除,因为要保证每一个开关都被经过了偶数次。
可惜直接这么做效果微乎其微,我们发现问题在于,如果直接把第$i$次访问到的底层的出口连向序列的第$i$个传感器,我们利用到的开关在底层中排布的是非常凌乱的,其实具体选那些出口没有什么太大关系,只要保证球滚出的顺序和要求的序列一样就可以了。我们可以只用$L$个底层出口中的后面$n$个,这么做就可以把所有利用到的开关分在一起,至于那些无用的开关就会被集中处理。这样节点的数量就会大大减少,总共有$logn$层开关,每层开关最多只有一个开关是不满的,所以不满的开关数最多$logn$个,由于满二叉树的节点数不会超过$n$个,故节点的数量就会在$n + logn$以内了。
$\bigodot$技巧&套路:
- 部分分对思路的禁锢和启发
- 线段树的思想,$Fft$的$rev$的意义
#include "doll.h" #include <vector> #include <algorithm> using namespace std; const int N = 500005; int n, L, tot = 1; int rev[N], bit[N], ps[N]; vector<int> a, c, x, y, to; int Solve(int l, int r) { if (l == r) return (l >= L - n)? a[ps[l]] : N; int md = (l + r) >> 1; int lc = Solve(l, md); int rc = Solve(md + 1, r); if (lc == N && rc == N) return N; x.push_back(lc); y.push_back(rc); return -x.size(); } void create_circuit(int m, vector<int> a0) { a = a0, n = a.size(); a.push_back(0); for (L = 1; L < n; L <<= 1); for (int i = 0; i < L; ++i) rev[i] = (rev[i >> 1] >> 1) | (i & 1? L >> 1 : 0); fill(bit, bit + L, -1); for (int i = L - n; i < L; ++i) bit[rev[i]] = i; for (int i = 0; i < L; ++i) if (bit[i] != -1) to.push_back(bit[i]); for (int i = 0; i < to.size(); ++i) ps[to[i]] = i + 1; int rt = Solve(0, L - 1); c.resize(m + 1, rt); c[0] = a[0]; for (int &i : x) if (i == N) i = rt; for (int &i : y) if (i == N) i = rt; answer(c, x, y); }