华英蒟蒻杯内部赛∀x∀题解
题意:
给定一个数字 $N$ 和一个字符串 $S$ ,问满足如下条件的字符串有几个
- 长度为$N$
- 该字符串为回文串
- 该字符串的字典序 $\le$ $S$的字典序
多组测试数据,$N$的总和 $\le$ $10^6$
思路:
看完题,em...要用数学方法,我不会懒得想,其实这不就是数位DP吗
我们现在分别处理每个条件
- 长度为$N$
这没啥好说的,就每一位单独处理
- 回文串
既然他是个回文串,就说明我们只需要枚举前面一半,就可以知道后面的部分
- 字典序 $\le$ $S$
就依据数位DP那样,用一个参数记录,当前枚举到的那位前面部分是否和 $S$ 相等,如果相等的话当前这一位就不能大过 $S$ 的这一位,否则就都可以。
举个栗子:
题目给的字符串$S$ : $abcdefghi$
当前枚举的字符串 :$abcde*???$
如果当前枚举到 $* $ 的位置,我们发现:
只要 $* $ 比 $f$ 大,那不管后面是什么都不可能使得当前枚举的字符串比 $S$ 小
如果 $* $ 就是 $f$ ,那就继续像这样判断下一位
如果 $* $ 比 $f$ 小,那后面是啥都不会使得当前枚举的字符串比 $S$ 大
但这时新问题出现了,如果我们前面一半和 $S$ 相等,但后面回文的那部分比 $S$ 大,那不就不合法了吗,所以我们只需要再开一个参数,记录后半部分是否等于或大于 $S$ ,这样就可以写代码了
代码:
#include<bits/stdc++.h>
using namespace std;
#define N 1000005
int t,n,ans,m,f[N][2][3];
const int mod=998244353;
char s[N];
int dfs(int k,int opt,int o){//记忆化搜索
int q=0;
if(k>m){
if(!opt||o!=2) return 1;//满足条件返回一
return 0;//否则是零
}
if(f[k][opt][o]!=-1) return f[k][opt][o];//记忆化
for(int i='A';i<='Z';i++){
if(opt&&i>s[k]) break;
/*
opt&&(i==s[k])意为:
如果前面的部分相等并且枚举的这一位相等,那就继续相等
否则为小于
*/
if(o==1){//后半部分=S的情况
if(i>s[n-k+1]) q+=dfs(k+1,opt&&(i==s[k]),2);
else if(i==s[n-k+1]) q+=dfs(k+1,opt&&(i==s[k]),1);
else q+=dfs(k+1,opt&&(i==s[k]),0);
}
else if(o==2){//后半部分>S的情况
if(i>s[n-k+1]) q+=dfs(k+1,opt&&(i==s[k]),2);
else if(i==s[n-k+1]) q+=dfs(k+1,opt&&(i==s[k]),2);
else q+=dfs(k+1,opt&&(i==s[k]),0);
}
else{//后半部分<S的情况
if(i>s[n-k+1]) q+=dfs(k+1,opt&&(i==s[k]),2);
else if(i==s[n-k+1]) q+=dfs(k+1,opt&&(i==s[k]),0);
else q+=dfs(k+1,opt&&(i==s[k]),0);
}
q%=mod;//取模
}
return f[k][opt][o]=q;//记忆化
}
int main(){
scanf("%d",&t);
while(t--){
ans=0;
scanf("%d",&n);
m=(n+1)/2;
scanf("%s",s+1);//读入
for(int i=1;i<=m+2;i++){//清空记忆化数组
f[i][0][0]=f[i][0][1]=f[i][0][2]=f[i][1][0]=f[i][1][1]=f[i][1][2]=-1;
}
ans=dfs(1,1,1);
printf("%d\n",ans);//输出
}
return 0;
}

浙公网安备 33010602011771号