Pa?sWorD

题目大意


给定一个字符串s,其中只包含小写字母,大写字母,数字和问号$?$。可以进行一下操作:

  • 将小写字母变为相对应的大写字母或不变;
  • 将问号$?$变为大写字母,小写字母或数字中的任意一个字符。

要求满足以下条件的所有方案数的数量:

  • 字符串中至少有一个小写字母;
  • 字符串中至少有一个大写字母;
  • 字符串中至少有一个数字;
  • 字符串中任意相邻字符不能相同。

解题思路


先考虑简单的三维$dp[i][j][k]$,表示第$i$个字符为$j$且前$i$个字符中各种字符的存在状态为$k$的合法数量。

其中$k$为一个三位二进制数,如:

  • 100表示只存在数字;
  • 010表示只存在大写字母;
  • 001表示只存在小写字母。

剩余状态也是类似的道理。

然后考虑状态转移,转移的过程只有一个限制条件,即相邻字符不能相同,则状态转移如下:

$$dp[i][x][k|t]=dp[i-1][j][k],x!=j$$

其中,当$x$分别为小写字母,大写字母,数字的时候$t$分别为$1$,$2$,$4$。

假设当前遍历到小写字母,则当它为大、小写字母的时候分别进行状态转移;假设遍历到问号,则遍历所有可以变成的字符进行转移。

时间复杂度(O(n*8*8*70))空间复杂度(O(n*8*8*70))

真巧,全都超限。

对于空间复杂度,仔细观察转移方程发现,第$i$项只第$i-1$项有关,故可以考虑滚动数组优化;至于时间复杂度,则可以考虑使用差分优化。

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include<bits/stdc++.h>
#define maxn 70
#define mod 998244353
using namespace std;
int d[maxn][1<<3],g[maxn][1<<3],dis[1<<3][maxn];
map<char,int>num;
void init(){
int cur=0;
for(int i='a';i<='z';++i) num[char(i)]=++cur;
for(int i='A';i<='Z';++i) num[char(i)]=++cur;
for(int i='0';i<='9';++i) num[char(i)]=++cur;
num['?']=1000;
}
void solve() {
int n,ans=0; cin >> n;
string s; cin >> s;
s=" "+s;
d[0][0]=1;
for(int i=1;i<=n;++i){
memcpy(g,d,sizeof(d));
memset(dis,0,sizeof(dis));
memset(d,0,sizeof(d));
for(int j=0;j<=62;++j){
for(int state=0;state<8;++state){
if(!g[j][state]||num[s[i]]==j&&num[s[i]]>=27) continue;
int tmp=g[j][state];
if(s[i]=='?'){
int sta=state|1;
if(j>=1&&j<=26) dis[sta][1]=(dis[sta][1]+tmp)%mod,dis[sta][j]=(dis[sta][j]-tmp)%mod,dis[sta][j+1]=(dis[sta][j+1]+tmp)%mod,dis[sta][27]=(dis[sta][27]-tmp)%mod;
else dis[sta][1]=(dis[sta][1]+tmp)%mod,dis[sta][27]=(dis[sta][27]-tmp)%mod;
sta=state|2;
if(j>=27&&j<=52) dis[sta][27]=(dis[sta][27]+tmp)%mod,dis[sta][j]=(dis[sta][j]-tmp)%mod,dis[sta][j+1]=(dis[sta][j+1]+tmp)%mod,dis[sta][53]=(dis[sta][53]-tmp)%mod;
else dis[sta][27]=(dis[sta][27]+tmp)%mod,dis[sta][53]=(dis[sta][53]-tmp)%mod;
sta=state|4;
if(j>=53&&j<=62) dis[sta][53]=(dis[sta][53]+tmp)%mod,dis[sta][j]=(dis[sta][j]-tmp)%mod,dis[sta][j+1]=(dis[sta][j+1]+tmp)%mod,dis[sta][63]=(dis[sta][63]-tmp)%mod;
else dis[sta][53]=(dis[sta][53]+tmp)%mod,dis[sta][63]=(dis[sta][63]-tmp)%mod;
}
else if(num[s[i]]<=26){
int cur=num[s[i]],sta=state|1;
if(cur!=j) d[cur][sta]=(d[cur][sta]+tmp)%mod;
cur=num[s[i]]+26,sta=state|2;
if(cur!=j) d[cur][sta]=(d[cur][sta]+tmp)%mod;
}
else if(num[s[i]]<=62&&num[s[i]]>=27){
int cur=num[s[i]],sta;
if(cur>=27&&cur<=52) sta=state|2;
else if(cur>=53&&cur<=62) sta=state|4;
d[cur][sta]=(d[cur][sta]+tmp)%mod;
}
}
}
for(int state=0;state<8;++state){
int cur=0;
for(int j=1;j<=62;++j){
cur=(cur+dis[state][j])%mod;
d[j][state]=(d[j][state]+cur)%mod;
}
}
}
for(int i=1;i<=65;++i) ans=(ans+d[i][7])%mod;
cout << (ans%mod+mod)%mod << '\n';
}

signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
if(fopen("in.txt", "r")) {
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
}
init();
int t=1;
while(t--) {
solve();
}
return 0;
}