1 条题解
-
0
题解
问题转化
设我们选择的结尾字母集合为 (,每个数字对应一个字母)。我们需要将字符串分割成若干单词,每个单词长度 且最后一个字母属于 。
关键观察:这样的分割存在当且仅当以下两个条件同时成立:
- 字符串的最后一个字符 。
- 所有长度为 的连续子串(窗口)都至少包含一个 中的字母。
必要性:若存在一个长度为 的窗口不含 中的字母,则该窗口无法被任何一个长度 的单词覆盖(因为单词的结尾必须在 中,而该窗口内没有 字母,且窗口长度为 ,任何跨越该窗口的单词长度都会超过 )。最后一个字符必须属于 是显然的。
充分性:当上述两个条件满足时,可以采用贪心策略:从位置 开始,每次选择当前能到达的最远位置 (满足 是 中的字母且 ),然后移动到 。由于每个长度为 的窗口都有 字母,这个过程总能继续,最终覆盖整个字符串。
因此,问题简化为:寻找最小的字母集合 ,使得:
- ;
- 对每个长度为 的窗口, 与该窗口的字母集合有交集。
集合覆盖的补集视角
记所有字母的全集为 ,用二进制掩码表示子集。对于每个窗口,计算该窗口内出现过的字母的掩码 。条件 等价于 ,其中 $\overline{\text{win\_mask}} = U \setminus \text{win\_mask}$ 是窗口内未出现字母的集合。
因此,一个 是“坏”的(即不满足条件)当且仅当存在某个窗口使得 ,即 是某个 的子集。我们只需要找出所有这样的坏 ,那么好的 就是剩下的那些,并且还需要包含最后一个字符。
算法步骤
-
特殊情况:若 ,则整个字符串可以作为一个单词,只需最后一个字符对应的格,答案为 。
-
预处理窗口:
- 计算所有长度为 的窗口的字母掩码。使用滑动窗口,维护每个字母在当前窗口中的出现次数和当前窗口的掩码,每次移动 更新。
- 令 。对每个窗口掩码 ,计算 ,并标记 。
-
子集传播(SOS DP):
- 对于每个 从 到 ,遍历所有掩码 从 到 ,若 包含第 位,则 $\text{bad}[\text{mask} \oplus (1 \ll i)] \mid= \text{bad}[\text{mask}]$。
- 结束后, 当且仅当存在某个窗口的补集包含 ,即 是某个 的子集,也就是 是坏的。
-
寻找最小好集合:
- 设 。
- 枚举所有掩码 ,若 且 ,则 是一个可行的 。记录其中 的最小值即为答案。
复杂度分析
- 滑动窗口计算所有窗口掩码:。
- SOS DP:。
- 枚举所有掩码:。
- 由于所有测试用例的 之和 ,所有 之和 ,总复杂度 ,在 时约为 ,完全可行。
代码实现(C++)
#include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { int n, c, k; cin >> n >> c >> k; string s; cin >> s; if (n < k) { // 整个字符串作为一个单词,只需最后一个字母 cout << "1\n"; continue; } int full = (1 << c) - 1; vector<int> bad(full + 1, 0); vector<int> cnt(c, 0); // 第一个窗口 [0, k-1] int win_mask = 0; for (int i = 0; i < k; ++i) { int ch = s[i] - 'A'; if (cnt[ch] == 0) win_mask |= (1 << ch); cnt[ch]++; } int comp = full ^ win_mask; bad[comp] = 1; // 滑动窗口 for (int i = k; i < n; ++i) { int left = s[i - k] - 'A'; cnt[left]--; if (cnt[left] == 0) win_mask ^= (1 << left); int right = s[i] - 'A'; if (cnt[right] == 0) win_mask |= (1 << right); cnt[right]++; comp = full ^ win_mask; bad[comp] = 1; } // SOS DP: 传播子集 for (int i = 0; i < c; ++i) { for (int mask = 0; mask <= full; ++mask) { if (mask >> i & 1) { bad[mask ^ (1 << i)] |= bad[mask]; } } } int last_bit = 1 << (s.back() - 'A'); int ans = c + 1; for (int mask = 0; mask <= full; ++mask) { if (!bad[mask] && (mask & last_bit)) { ans = min(ans, __builtin_popcount(mask)); } } cout << ans << '\n'; } return 0; }
- 1
信息
- ID
- 7244
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 2
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者