问题
输入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]);
}
}