题目描述
歌利亚(Gollum\texttt{Gollum}Gollum)在摩瑞亚(Moria\texttt{Moria}Moria)的黑暗中生活了五百年,他的眼睛适应了黑暗,但也因此患上了阅读障碍。他特别讨厌包含长回文子串的字符串。歌利亚有一个容忍度 KKK ,这意味着他可以阅读一个单词,只要该单词不包含任何长度大于等于 KKK 的回文子串。
给定 NNN 和 KKK ,计算长度为 NNN 的二进制字符串中,歌利亚能够容忍阅读的数量。答案需要对 1,000,000,0071,000,000,0071,000,000,007 取模。
输入格式
- 第一行包含一个整数 TTT ,表示测试用例的数量。
- 每个测试用例包含一行两个整数 NNN 和 KKK 。
输出格式
对于每个测试用例,输出答案对 1,000,000,0071,000,000,0071,000,000,007 取模的结果。
数据范围
- 1≤T≤1001 \leq T \leq 1001≤T≤100
- 1≤N≤4001 \leq N \leq 4001≤N≤400
- 1≤K≤101 \leq K \leq 101≤K≤10
题目分析
问题本质
我们需要计算长度为 NNN 的二进制字符串(只包含字符 0 和 1 )中,不包含任何长度大于等于 KKK 的回文子串的字符串数量。
关键观察
-
回文子串的性质:如果一个字符串包含长度 ≥K\geq K≥K 的回文子串,那么它一定包含长度恰好为 KKK 或 K+1K+1K+1 的回文子串。这是因为更长的回文总是包含更短的回文中心部分。
-
二进制字符串的特殊性:由于只有两种字符,回文的模式受到限制。例如:
- 长度为 333 的回文必然是
000或111。 - 长度为 444 的回文必然是
0000、0110、1001或1111。
- 长度为 333 的回文必然是
-
避免长度 ≥K\geq K≥K 的回文等价于避免所有长度为 KKK 和 K+1K+1K+1 的回文子串。
解题思路
这个问题可以通过状态压缩动态规划(DP\texttt{DP}DP)来解决。
状态设计
我们关心字符串的最后若干位,因为新添加一个字符时,只需要检查新形成的后缀是否包含回文。具体来说,我们需要知道:
- 最后 KKK 位:用于检查是否形成了长度为 KKK 的回文。
- 最后 K+1K+1K+1 位:用于检查是否形成了长度为 K+1K+1K+1 的回文。
因此,我们可以将最后 K+1K+1K+1 位作为一个状态,用二进制数表示。由于 K≤10K \leq 10K≤10 ,状态数最多为 211=20482^{11} = 2048211=2048 ,在可接受范围内。
状态表示
设 dp[i][mask]dp[i][mask]dp[i][mask] 表示长度为 iii 的字符串,且最后 K+1K+1K+1 位(如果 i<K+1i < K+1i<K+1 则不足的位用 000 填充高位)的二进制表示为 maskmaskmask 的有效字符串数量。
状态转移
对于当前状态 dp[i][mask]dp[i][mask]dp[i][mask] ,我们可以尝试在字符串末尾添加一个字符 0 或 1 :
- 新字符 bitbitbit ( 000 或 111 )。
- 计算新的状态 newMasknewMasknewMask :将 maskmaskmask 左移一位,去掉最高位,然后在最低位加入 bitbitbit 。
newMask=((mask≪1)&((1≪(K+1))−1))∣bit newMask = ((mask \ll 1) \& ((1 \ll (K+1)) - 1)) \mid bit newMask=((mask≪1)&((1≪(K+1))−1))∣bit - 检查新字符串是否包含长度 ≥K\geq K≥K 的回文:
- 如果 i+1≥Ki+1 \geq Ki+1≥K ,检查 newMasknewMasknewMask 的低 KKK 位是否为回文。
- 如果 i+1≥K+1i+1 \geq K+1i+1≥K+1 ,检查 newMasknewMasknewMask 的全部 K+1K+1K+1 位是否为回文。
- 如果以上检查都不成立,则新字符串有效,更新 dp[i+1][newMask]dp[i+1][newMask]dp[i+1][newMask] 。
初始化
- 当 i=1i=1i=1 时,字符串只有 111 位。初始化 dp[1][0]=1dp[1][0]=1dp[1][0]=1 (以
0结尾)和 dp[1][1]=1dp[1][1]=1dp[1][1]=1 (以1结尾)。
特殊情况处理
- K=1K=1K=1 :任何单个字符都是长度为 111 的回文,所以没有有效字符串,答案为 000 。
- K>NK > NK>N :不可能存在长度 ≥K\geq K≥K 的回文子串,所有 2N2^N2N 个字符串都有效。
答案计算
最终答案为所有长度为 NNN 的状态之和:
answer=∑mask=02K+1−1dp[N][mask]
\text{answer} = \sum_{mask=0}^{2^{K+1}-1} dp[N][mask]
answer=mask=0∑2K+1−1dp[N][mask]
算法复杂度
- 状态数: O(N×2K+1)O(N \times 2^{K+1})O(N×2K+1)
- 每个状态转移 O(1)O(1)O(1)
- 总复杂度: O(T×N×2K+1)O(T \times N \times 2^{K+1})O(T×N×2K+1) ,对于 N≤400,K≤10N \leq 400, K \leq 10N≤400,K≤10 完全可行。
代码实现
// Dyslexic Gollum
// UVa ID: 1633
// Verdict: Accepted
// Submission Date: 2025-12-09
// UVa Run Time: 0.310s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
const int MOD = 1000000007; // 模数
const int MAX_N = 405; // 最大长度
const int MAX_K = 11; // 最大K+1,用于状态表示
int dp[MAX_N][1 << MAX_K]; // dp数组
bool palK[1 << MAX_K]; // 标记长度为K的回文
bool palK1[1 << MAX_K]; // 标记长度为K+1的回文
// 检查一个二进制数的低len位是否是回文
bool checkPalindrome(int mask, int len) {
for (int i = 0; i < len / 2; i++) {
int left = (mask >> i) & 1; // 第i位
int right = (mask >> (len - 1 - i)) & 1; // 对称位置
if (left != right) return false;
}
return true;
}
// 主计算函数
int solve(int N, int K) {
// 特殊情况处理
if (K == 1) return 0; // 单个字符就是回文,无解
if (K > N) { // 不可能有长度>=K的回文
long long ans = 1;
for (int i = 0; i < N; i++) ans = (ans * 2) % MOD;
return ans;
}
// 预处理回文标记
int maxMaskK = 1 << K; // K位状态总数
int maxMaskK1 = 1 << (K + 1); // K+1位状态总数
// 标记所有长度为K的回文
for (int mask = 0; mask < maxMaskK; mask++)
palK[mask] = checkPalindrome(mask, K);
// 标记所有长度为K+1的回文
for (int mask = 0; mask < maxMaskK1; mask++)
palK1[mask] = checkPalindrome(mask, K + 1);
// 初始化dp数组
for (int i = 0; i <= N; i++)
for (int mask = 0; mask < maxMaskK1; mask++)
dp[i][mask] = 0;
// 初始状态:长度为1的字符串
dp[1][0] = 1; // 以0结尾
dp[1][1] = 1; // 以1结尾
// 动态规划转移
for (int i = 1; i < N; i++) {
for (int mask = 0; mask < maxMaskK1; mask++) {
if (dp[i][mask] == 0) continue; // 无效状态
// 尝试添加0或1
for (int bit = 0; bit <= 1; bit++) {
// 计算新状态:左移一位,去掉最高位,添加新位
int newMask = ((mask << 1) & ((1 << (K + 1)) - 1)) | bit;
bool valid = true;
// 检查是否形成长度为K的回文
if (i + 1 >= K) {
int lastK = newMask & ((1 << K) - 1); // 取低K位
if (palK[lastK]) {
valid = false;
}
}
// 检查是否形成长度为K+1的回文
if (i + 1 >= K + 1) {
if (palK1[newMask]) {
valid = false;
}
}
// 如果有效,更新dp值
if (valid) {
dp[i + 1][newMask] = (dp[i + 1][newMask] + dp[i][mask]) % MOD;
}
}
}
}
// 计算结果:所有长度为N的状态之和
int result = 0;
for (int mask = 0; mask < maxMaskK1; mask++)
result = (dp[N][mask] + result) % MOD;
return result;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int T;
cin >> T;
while (T--) {
int N, K;
cin >> N >> K;
cout << solve(N, K) << "\n";
}
return 0;
}
总结
本题的关键在于将“不包含长度 ≥K\geq K≥K 的回文子串”转化为“不包含长度恰好为 KKK 或 K+1K+1K+1 的回文子串”,然后通过状态压缩动态规划来计数。状态设计为字符串的最后 K+1K+1K+1 位,这样可以在添加新字符时快速检查是否形成了回文。算法的时间复杂度在题目限制范围内,可以高效解决问题。
357

被折叠的 条评论
为什么被折叠?



