LOJ 3115 「SDOI2019」连续子序列
首先注意到题目的 \(k\) 是非常大的,所以这题的一个核心应当是缩减 \(k\) 的范围。
首先观察 \(T_{2n} = T_n, T_{2n + 1} = 1 - T_n\) 这个式子。
因为这里的下标变换都是 \(\times 2\) 的,所以可以放在二进制意义下考虑。
那么 \(2n\) 相对于 \(n\) 就是末尾多了一个 \(0\),\(2n + 1\) 相对于 \(n\) 就是末尾多了一个 \(1\)。
结合上 \(T_{2n + 1} = 1 - T_n\),就可以当作是二进制每在后面加一个 \(1\) 值就取反。
于是可以得到式子 \(T_{i} = \operatorname{popcount}(i)\bmod 2\)。
但是根据这个形式对于这题还是不好做,因为并不好判断串 \(S\) 是从哪个 \(i\) 开始的,需要考虑的数量依然很多。
所以接下来考虑找一些其他的形式的刻画。
因为式子是与二进制有关的,所以形式的刻画也就从二进制来思考。
一个想法是每次增加一个高位。
考虑对比 \(0\sim 2^h - 1\) 和 \(2^h\sim 2^{h + 1} - 1\) 两部分,唯一的区别就是第 \(h\) 位前一部分为 \(0\),后一部分为 \(1\)。
所以有递推式 \(S' = S + \operatorname{flip}(S)\)。
但是套用到这题的询问上,还需要枚举这个 \(S\) 与 \(\operatorname{flip}(S)\) 的分界线,似乎也不能很好的缩小问题。
既然有增加高位,那么也可以考虑增加低位。
既然是增加低位,为了保持 \(S\) 原本的顺序和值域的连续,对于 \(S\) 的每一个数就要在低位加一个 \(0\) 加一个 \(1\)。
那么如果是加 \(0\),那么 \(\operatorname{popcount}\) 奇偶性不变,如果是加 \(1\),那么 \(\operatorname{popcount}\) 奇偶性改变。
于是这个加低位的过程相当于是 \(0\to 01, 1\to 10\)。
注意到这个加低位的过程起到了长度 \(\times 2\) 的作用,这个 \(\times 2\) 看起来对问题大小的缩减有点帮助。
于是考虑 \(\div 2\),也就是去低位。
对于去低位的操作,就可以当作是加低位的逆过程:\(01\to 0, 10\to 1\)。
那么如果串 \(S\) 已知,就可以把 \(S\) 按顺序划分成许多个长度为 \(2\) 的小串(特殊处理一下开头和末尾单个的情况),然后对于小串缩减长度为 \(1\) 再合并成新的串 \(S'\),如果存在 \(00\) 或者 \(11\) 就说明划分不合法。
这样子做下去,就实现了 \(|S|\) 折半。
但是还有一个问题是,对于串 \(S\),有两种划分方式:\([1, 2], \cdots\) 和 \([1, 1], [2, 3], \cdots\)。
看起来问题规模还是没有优化下去。
如果尝试手玩,应该会发现许多串其实只会有一种合法的划分方式变到长度较短的情况。
接下来,给出一个定理:当 \(|S| > 3\) 时,使 \(|S|\) 被缩减到 \(\le 3\) 的划分方案只有一种。
证明:
考虑到如果划分方案中出现 \(00\) 或者 \(11\) 就不合法,那么考虑根据是否有 \(00\) 或者 \(11\) 分讨:
- \(S\) 中存在 \(00\) 或者 \(11\)。
那么因为不能被划分进一个小串,那么 \(00\) 和 \(11\) 就直接排除了至少一种划分,所以此时至多存在 \(1\) 种合法的划分递归下去。 - \(S\) 中不存在 \(00\) 和 \(11\)。
此时 \(S\) 一定成 \(0, 1\) 交替的形态。
那么当 \(|S| \ge 5\) 时,两种划分的方案递归下去为全 \(0\) 和全 \(1\),且此时新串长度 \(\ge 3\),那么一定不合法。
当 \(|S| = 4\),就只有 \(0101\) 和 \(1010\) 两种情况,可以直接验证只有一种合法划分。
于是直接划分下去,当 \(|S|\le 3\) 时特判就是可以了。
那么接下来考虑计数。
根据前文,每个 \(|S| > 3\) 的串存在唯一划分方式。
那么这就说明当 \(|S| + k\le 3\) 时暴力判断,否则直接递归下去是不会出现算重的情况的。
对于如何递归,只需要考虑把 \((S, k)\) 当作状态,尝试两种划分情况继续搜下去,搜索过程中记忆化即可。
对于状态数,考虑分两个阶段考虑:
- 当 \(|S| > 3\) 时,仍然只存在一种划分方式,于是状态数不超过 \(2 \log |S|\)。
- 当 \(|S|\le 3\) 时,合法的 \(S\) 的数量只有 \(12\) 个。
考虑到 \(k\) 在每次递归后只可能变成两个值: \(\lceil\frac{k}{2}\rceil, \lceil\frac{k - 1}{2}\rceil = \lfloor\frac{k}{2}\rfloor\),此时有 \(\lceil\frac{k}{2}\rceil - \lfloor\frac{k}{2}\rfloor \le 1\)。
继续递归一层,\(k'\) 最大值为 \(\lceil\frac{\lceil\frac{k}{2}\rceil}{2}\rceil = \lceil\frac{k}{4}\rceil\),最小值为 \(\lfloor\frac{\lfloor\frac{k}{2}\rfloor}{2}\rfloor = \lfloor\frac{k}{4}\rfloor\),依然有 \(\lceil\frac{k}{4}\rceil - \lfloor\frac{k}{4}\rfloor \le 1\)。
于是可以当作递归 \(\log k\) 层,每一层至多有 \(2\) 个本质不同的 \(k\),于是状态数不超过 \(24\log k\)。
于是状态数可以当作是 \(\mathcal{O}(\log |S| + \log k)\) 的。
于是直接记忆化搜索的复杂度就为 \(\mathcal{O}(T(\log |S| + \log k)\log (\log |S| + \log k))\)。
#include<bits/stdc++.h>
using ll = long long;
constexpr ll mod = 1e9 + 9;
std::map<std::pair<std::string, int>, ll> f;
ll dfs(std::string s, ll k) {
auto p = std::make_pair(s, k);
if (f.count(p)) return f[p];
ll &ans = f[p];
if (s.size() + k <= 3) {
int pre = 0;
for (int i = 0; i < s.size(); i++) pre |= (s[i] == '1') << i;
for (int t = 0; t < (1 << k); t++) {
int mask = pre | (t << s.size());
if (s.size() + k == 3 && (mask == 0 || mask == 7)) continue;
ans++;
}
return ans;
}
std::string t; bool ok = true;
for (int i = 0; i < s.size(); i += 2) {
if (i + 1 < s.size() && s[i] == s[i + 1]) ok = false;
t += s[i];
}
if (ok) ans += dfs(t, s.size() & 1 ? (k / 2) : ((k + 1) / 2));
t = s[0] == '0' ? "1" : "0", ok = true;
for (int i = 1; i < s.size(); i += 2) {
if (i + 1 < s.size() && s[i] == s[i + 1]) ok = false;
t += s[i];
}
if (ok) ans += dfs(t, s.size() & 1 ? ((k + 1) / 2) : (k / 2));
return ans %= mod;
}
int main() {
int T; scanf("%d", &T);
while (T--) {
std::string s; ll k;
std::cin >> s >> k;
std::cout << dfs(s, k) << '\n';
}
return 0;
}
浙公网安备 33010602011771号