JOI2018 Snake Escaping
好神奇的做法,我称其为猪猪(猪笼原理)分治。
记 \(0,1,?\) 的个数分别为 \(a,b,c\)。有一个显然的 \(O(2^c)\) 做法,对每个 \(?\) 枚举其为 \(0/1\) 即可。
然后我们考虑只有 \(?,1\) 的情况,把所有 \(?\) 当成 \(0\),答案就是一个超集和;同理,对于只有 \(?,0\) 的情况,把所有 \(?\) 当成 \(1\),答案就是一个子集和。两者都可以用 FWT 预处理出来。
于是我们考虑把 \(0\) 和 \(1\) 的其中一个用 \(?\) 来替换。比方说 \(0\) 换成 \(?\),根据容斥,应该减去 \(?\) 为 \(1\) 的情况。这同样可以轻易地解决,于是我们得到了 \(O(2^a)\) 和 \(O(2^b)\) 的做法。
综合一下,如果我们每次只选 \(a,b,c\) 中较小的做法来做,复杂度为 \(O(2^{\min\{a,b,c\}})\)。根据猪笼(抽屉)原理,\(\min\{a,b,c\}\le \left\lfloor\dfrac{L}{3}\right\rfloor\),所以总复杂度为 \(O(n2^n+q2^{L/3})\),能过。
int dfs0(int st, int dep) {
if (dep == n) return g[st];
if (s[dep] == '0') return dfs0(st << 1, dep + 1) - dfs0(st << 1 | 1, dep + 1);
if (s[dep] == '?') return dfs0(st << 1, dep + 1);
return dfs0(st << 1 | 1, dep + 1);
}
int dfs1(int st, int dep) {
if (dep == n) return f[st];
if (s[dep] == '1') return dfs1(st << 1 | 1, dep + 1) - dfs1(st << 1, dep + 1);
if (s[dep] == '?') return dfs1(st << 1 | 1, dep + 1);
return dfs1(st << 1, dep + 1);
}
int dfs2(int st, int dep) {
if (dep == n) return a[st];
if (s[dep] == '?') return dfs2(st << 1, dep + 1) + dfs2(st << 1 | 1, dep + 1);
return dfs2(st << 1 | (s[dep] - '0'), dep + 1);
}
int main() {
n = read(), q = read(), S = (1 << n);
for (int i = 0; i < S; i++) scanf("%1d", &a[i]), f[i] = g[i] = a[i];
for (int o = 2, k = 1; o <= S; o <<= 1, k <<= 1)
for (int i = 0; i < S; i += o)
for (int j = 0; j < k; j++)
f[i + j + k] += f[i + j], g[i + j] += g[i + j + k];
while (q--) {
scanf("%s", s);
int a = 0, b = 0, c = 0;
for (int i = 0; i < n; i++)
a += (s[i] == '0'), b += (s[i] == '1'), c += (s[i] == '?');
if (c <= n / 3) write(dfs2(0, 0));
else if (a <= n / 3) write(dfs0(0, 0));
else write(dfs1(0, 0));
puts("");
}
return 0;
}