UVa 1633 Dyslexic Gollum

题目描述

歌利亚(Gollum\texttt{Gollum}Gollum)在摩瑞亚(Moria\texttt{Moria}Moria)的黑暗中生活了五百年,他的眼睛适应了黑暗,但也因此患上了阅读障碍。他特别讨厌包含长回文子串的字符串。歌利亚有一个容忍度 KKK ,这意味着他可以阅读一个单词,只要该单词不包含任何长度大于等于 KKK 的回文子串。

给定 NNNKKK ,计算长度为 NNN二进制字符串中,歌利亚能够容忍阅读的数量。答案需要对 1,000,000,0071,000,000,0071,000,000,007 取模。

输入格式

  • 第一行包含一个整数 TTT ,表示测试用例的数量。
  • 每个测试用例包含一行两个整数 NNNKKK

输出格式

对于每个测试用例,输出答案对 1,000,000,0071,000,000,0071,000,000,007 取模的结果。

数据范围

  • 1≤T≤1001 \leq T \leq 1001T100
  • 1≤N≤4001 \leq N \leq 4001N400
  • 1≤K≤101 \leq K \leq 101K10

题目分析

问题本质

我们需要计算长度为 NNN 的二进制字符串(只包含字符 01 )中,不包含任何长度大于等于 KKK 的回文子串的字符串数量。

关键观察

  1. 回文子串的性质:如果一个字符串包含长度 ≥K\geq KK 的回文子串,那么它一定包含长度恰好为 KKKK+1K+1K+1 的回文子串。这是因为更长的回文总是包含更短的回文中心部分。

  2. 二进制字符串的特殊性:由于只有两种字符,回文的模式受到限制。例如:

    • 长度为 333 的回文必然是 000111
    • 长度为 444 的回文必然是 0000011010011111
  3. 避免长度 ≥K\geq KK 的回文等价于避免所有长度为 KKKK+1K+1K+1 的回文子串

解题思路

这个问题可以通过状态压缩动态规划DP\texttt{DP}DP)来解决。

状态设计

我们关心字符串的最后若干位,因为新添加一个字符时,只需要检查新形成的后缀是否包含回文。具体来说,我们需要知道:

  • 最后 KKK 位:用于检查是否形成了长度为 KKK 的回文。
  • 最后 K+1K+1K+1 位:用于检查是否形成了长度为 K+1K+1K+1 的回文。

因此,我们可以将最后 K+1K+1K+1作为一个状态,用二进制数表示。由于 K≤10K \leq 10K10 ,状态数最多为 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] ,我们可以尝试在字符串末尾添加一个字符 01

  1. 新字符 bitbitbit000111 )。
  2. 计算新的状态 newMasknewMasknewMask :将 maskmaskmask 左移一位,去掉最高位,然后在最低位加入 bitbitbit
    newMask=((mask≪1)&((1≪(K+1))−1))∣bit newMask = ((mask \ll 1) \& ((1 \ll (K+1)) - 1)) \mid bit newMask=((mask1)&((1(K+1))1))bit
  3. 检查新字符串是否包含长度 ≥K\geq KK 的回文:
    • 如果 i+1≥Ki+1 \geq Ki+1K ,检查 newMasknewMasknewMaskKKK是否为回文。
    • 如果 i+1≥K+1i+1 \geq K+1i+1K+1 ,检查 newMasknewMasknewMask全部 K+1K+1K+1是否为回文。
  4. 如果以上检查都不成立,则新字符串有效,更新 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 结尾)。
特殊情况处理
  1. K=1K=1K=1 :任何单个字符都是长度为 111 的回文,所以没有有效字符串,答案为 000
  2. K>NK > NK>N :不可能存在长度 ≥K\geq KK 的回文子串,所有 2N2^N2N 个字符串都有效。
答案计算

最终答案为所有长度为 NNN 的状态之和:
answer=∑mask=02K+1−1dp[N][mask] \text{answer} = \sum_{mask=0}^{2^{K+1}-1} dp[N][mask] answer=mask=02K+11dp[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 10N400,K10 完全可行。

代码实现

// 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 KK 的回文子串”转化为“不包含长度恰好为 KKKK+1K+1K+1 的回文子串”,然后通过状态压缩动态规划来计数。状态设计为字符串的最后 K+1K+1K+1 位,这样可以在添加新字符时快速检查是否形成了回文。算法的时间复杂度在题目限制范围内,可以高效解决问题。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值