UVA - 1633 Dyslexic Gollum 状态压缩

问题

输入N,K(N<=400,K<=10),求长度为N且不含有长度至少为K的连续回文子串的01字符串有多少个?

分析

输入k,要判断k,k+1的情况,因为一个字符串有长度为k的回文子串,那么一定有长度为k-2的回文子串,所以长度大于等于k的情况,可以被合并成两种,长度为k和长度为k+1,所以当k=10,要判断最后11位是否是回文
参考:
https://blog.csdn.net/dilemma729/article/details/43984541
https://blog.csdn.net/chy20142109/article/details/52266353
https://blog.csdn.net/u014258433/article/details/70038040
使用字符串结尾的字符作为一个维度,字符长度N作为一个维度,然后输入的K作为一个维度,使用状态压缩表示字符串结尾的状态

maxlen[i][j]代表长度是i(i<=11),值是j的连续字符中回文子串的最长长度,为了计算dp[i][j][k]提供预处理
状态转移方程:
maxlen[i][j]=i (j是回文子串)
maxlen[i][j]=max(maxlen[i-1][j&((1<<(i-1))-1)],maxlen[i-1][j>>1]) (j不是回文子串)

i<=11的情况不需要计算使用dp数组计算
dp[i][j][k]表示长度为i(1<=400)的结尾的值是j(j < 1<<11)的情况下,回文子串最长小于k的情况有多少个
状态转移方程“
dp[i][j][k]=dp[i-1][j>>1][k]+dp[i-1][(j>>1)|(1<<10)][k] (j中有长度至少为k的回文子串)
dp[i][j][k]=0 (j中没有长度至少为k的回文子串)

#include<iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define MOD 1000000007
using namespace std;
const int maxn=405,maxk=11;
//dp+状态压缩,首先判断回文,使用字符最后的几位判断是否是超出长度的回文,若不是,可以删去最后一位,继续判断,形成状态转移
//先算出所有的结果,打表,再查表,输出结果
//dp[i][j][k]表示长度为i(1<=400)的结尾的值是j(j < 1<<11)的情况下,回文子串最长小于k的情况有多少个
//maxlen[i][j]代表长度是i(i<=11),值是j的连续字符中回文子串的最长长度,为了计算dp[i][j][k]提供预处理
//输入k,要判断k,k+1的情况,因为一个字符串有长度为k的回文子串,那么一定有长度为k-2的回文子串,所以长度大于等于k的情况
//可以被合并成两种,长度为k和长度为k+1,所以当k=10,要判断最后11位是否是回文
//例如110011100100111,光看后10位1100100111,回文长度为9,小于10,但是看后11位,回文长度为11,超过了10
int dp[maxn][(1<<maxk)+10][maxk+2],ans[maxn][maxk],maxlen[maxk+2][(1<<maxk)+10],temp[(1<<maxk)+10];
int T,N,K;

//预处理,为判断长度为i值为j的一个字符串中回文子串的最大长度打表
void init(){
//    maxlen[1][0]=1; maxlen[1][1]=1;
    for(int i=1;i<=11;++i){  //i代表长度
        int p=1<<i,half=i/2;  //half是j的长度的一半
        for(int j=0;j<p;++j){  //j代表长度为i,值为j的01序列,maxlen[i][j]是长为i值为j的连续序列中的最长回文子串长度
            bool plalindrome=true;
            for(int h=0;h<half;++h){  //用位运算判断j是不是回文的
                if(((j>>h)&1)!=((j>>(i-h-1))&1)){
                    plalindrome=false;
                    break;
                }
            }
            if(plalindrome) maxlen[i][j]=i;
            else{
                //既然j不是全部回文,那么j的首部或者尾部至少一个不在回文子串中,继续判断
                // j&((1<<(i-1))-1)去除首部,j>>1去除尾部
                maxlen[i][j]=max(maxlen[i-1][j&((1<<(i-1))-1)],maxlen[i-1][j>>1]);
            }
        }
    }
}
//打表,求解
void solve(){
    for(int i=1;i<=10;++i){
        int p=1<<i;
        for(int j=0;j<p;++j){   //长为i值为j,含有的最长回文子串长度是maxlen[i][j],所以长度要求从他开始,小于等于的全是0
            for(int k=maxlen[i][j]+1;k<=10;++k){  //要求的k必须大于maxlen[i][j],j才是合格的,否则回文长度等于或者超出
                ++ans[i][k];  //计数,j是一种合格情况,可以计入答案中
            }
        }
    }
    int p=1<<11; //最多看结尾11位的回文长度
    for(int j=0;j<p;++j){
        temp[j]=maxlen[11][j];  //直接抽出11位的情况,方便使用,不使用temp也可以,直接用maxlen[11][j]
    }
    //i=11要单算,因为要为i>11的迭代做基础
    for(int j=0;j<p;++j){
        //因为输入的k<=10
        for(int k=maxlen[11][j]+1;k<=10;++k){
            ++dp[11][j][k];
            ++ans[11][k];
        }
    }
    //dp数组用来保存dp的结果,ans才是真正的结果
    for(int i=12;i<=400;++i){  //遍历所有情况的长度
        for(int j=0;j<p;++j){   //结尾的11位值为j
            for(int k=1;k<=10;++k) { //遍历k的要求(回文最长长度)
//                if(temp[j]>=k){
//                    dp[i][j][k]=0;  没必要
//                    ans[i][k]=0;  没必要
//                }
                if(temp[j]<k){
                    //j一共11位,集体向右移1位,左边新的一位可能是0,也就是j>>1,也可能是1,所以是(j>>1)|(1<<10)
                    dp[i][j][k]=(dp[i-1][j>>1][k]+dp[i-1][(j>>1)|(1<<10)][k])%MOD;
                    ans[i][k]=(ans[i][k]+dp[i][j][k])%MOD;
                }
            }
        }
    }

}

int main(void){
    init();
    solve();
    scanf("%d",&T);
    for(int i=0;i<T;++i){
        scanf("%d%d",&N,&K);
        printf("%d\n",ans[N][K]);
    }
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值