题解:AT_abc359_d [ABC359D] Avoid K Palindrome
状压 dp 题。
思路
对于每个字符 :
- 如果 不为
A,那么它可以是B。我们要把这个状态算进去。 - 如果 不为
B,那么它可以是A。我们要把这个状态算进去。
令 表示当前枚举到了第 位,往前 位的二进制表示为一个 01 串 ,这个 01 串十进制表示为 。若 的从 倒数第 位为 A,则 的倒数第 位为 ,反之为 。显然 。
每次我们新加进来一个字符,都要把最前面的字符踢掉,然后把新的字符补进来。我们可以 地完成这个操作。令上一个 为 ,新更新的 为 ,则 ,其中 为当前的字符, 函数的返回值为:
- 若 不为
A, 函数返回 。 - 若 不为
B, 函数返回 。
特别地,如果 为 ?,说明两种都可能,两种都要计算。
状态转移方程显然,因为可能有多个原来不相同的 转移到相同的 ,所以:。
注意到我们需要要求它不为回文串。因此在程序开始时,我们要预处理出所有可能的回文串对应的 值,如果我们发现转移出的 代表的字符串是回文串,我们要把它舍去。
代码
#include<iostream>
#define int long long
using namespace std;
int dp[1005][1055],whe[1055];
int n,k;
string s;
int mier[]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768};
int get(int _){
if(_%2)return _/2+1;
else return _/2;
}
char ask(int y){
if(y%2)return 'B';
return 'A';
}
int reget(char ch){
if(ch=='A')return 0;
return 1;
}
void init(){
int p=get(k);
for(int i=0;i<=mier[k]-1;i++){
string t=" ";
int q=i,cnt=k;
for(int i=1;i<=k;i++){
t[cnt--]=ask(q%2);
q/=2;
}
bool f=1;
for(int i=1;i<=p;i++){
if(t[i]!=t[k-i+1]){
f=0;
break;
}
}
if(f==1)whe[i]=1;
}
}
signed main(){
cin>>n>>k>>s;s=' '+s;
init();
if(s[1]!='B')dp[1][0]=1;
if(s[1]!='A')dp[1][1]=1;
for(int i=2;i<k;i++){
for(int j=0;j<mier[k];j++){
int y=0;
if(s[i]!='B')dp[i][j<<1]=(dp[i][j<<1]+dp[i-1][j])%998244353;
if(s[i]!='A')dp[i][(j*2)+1]=(dp[i][(j*2)+1]+dp[i-1][j])%998244353;
}
}
for(int i=k;i<=n;i++){
for(int j=0;j<mier[k];j++){
int y=0;
if(s[i]!='B'&&(!whe[(j*2)&(mier[k]-1)]))dp[i][(j*2)&(mier[k]-1)]=(dp[i][(j*2)&(mier[k]-1)]+dp[i-1][j])%998244353;
if(s[i]!='A'&&(!whe[((j*2)+1)&(mier[k]-1)]))dp[i][((j*2)+1)&(mier[k]-1)]=(dp[i][((j*2)+1)&(mier[k]-1)]+dp[i-1][j])%998244353;
}
}
int ans=0;
for(int i=0;i<mier[k];i++)ans+=dp[n][i];
cout<<ans%998244353;
return 0;
}
提示
- 代码中运用了位运算,是一种极好的卡常方法。
- 本题答案较大,需要开
long long。
时间复杂度
预处理是 的。我们枚举字符串的十进制值是 的,判断回文是 的。
dp 是 的。首先我们枚举 ,是 的。然后我们枚举 ,是 的。
总体复杂度为 ,可以通过本题。

浙公网安备 33010602011771号