题解:AT_abc359_d [ABC359D] Avoid K Palindrome

状压 dp 题。

思路

对于每个字符 sis_i

  • 如果 sis_i 不为 A,那么它可以是 B。我们要把这个状态算进去。
  • 如果 sis_i 不为 B,那么它可以是 A。我们要把这个状态算进去。

dpi,jdp_{i,j} 表示当前枚举到了第 ii 位,往前 kk 位的二进制表示为一个 01 串 tt,这个 01 串十进制表示为 jj。若 ss 的从 ii 倒数第 pp 位为 A,则 tt 的倒数第 pp 位为 00,反之为 11。显然 j<2kj<2^k

每次我们新加进来一个字符,都要把最前面的字符踢掉,然后把新的字符补进来。我们可以 O(1)\operatorname{O}(1) 地完成这个操作。令上一个 jjj1j_1,新更新的 jjj2j_2,则 j2=(2×j1)2k+get(ch)j_2=(2\times j_1)-2^k+\operatorname{get}(ch),其中 chch 为当前的字符,get\operatorname{get} 函数的返回值为:

  • chch 不为 Aget\operatorname{get} 函数返回 11
  • chch 不为 Bget\operatorname{get} 函数返回 00

特别地,如果 chch?,说明两种都可能,两种都要计算。

状态转移方程显然,因为可能有多个原来不相同的 j1j_1 转移到相同的 j2j_2,所以:dpi,j2=(dpi1,j1+dpi,j2) mod998244353dp_{i,j_2}=(dp_{i-1,j_1}+dp_{i,j_2})\bmod 998244353

注意到我们需要要求它不为回文串。因此在程序开始时,我们要预处理出所有可能的回文串对应的 jj 值,如果我们发现转移出的 j2j_2 代表的字符串是回文串,我们要把它舍去。

代码

#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

时间复杂度

预处理是 O(2kk)\operatorname{O}(2^kk) 的。我们枚举字符串的十进制值是 O(2k)\operatorname{O}(2^k) 的,判断回文是 O(k)\operatorname{O}(k) 的。

dp 是 O(2kn)\operatorname{O}(2^kn) 的。首先我们枚举 ii,是 O(n)\operatorname{O}(n) 的。然后我们枚举 j1j_1,是O(2k)\operatorname{O}(2^k) 的。

总体复杂度为 O(2kn)\operatorname{O}(2^kn),可以通过本题。

posted @   Weslie_qwq  阅读(4)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示