题目 | Pa?sWorD
The 2023 ICPC Asia Regionals Online Contest (1)
I - Pa?sWorD
https://pintia.cn/market/item/1703381331863785472
Time Limit: 1000ms
Memory Limit: 32MB
Problem
You need to log into a mysterious system, but you realize you've forgotten your password. The system does not support resetting password, so the only way to log in is to keep trying.
Fortunately, you still remember some information about the password:
Firstly, you are sure that the length of the password is
Then the information you remember can be described by a string s of length
let
- if
is an uppercase letter or a digital character, then the i-th character of the password must be ; - if
is an lowercase letter, then the -th character of the password might be or the uppercase form of ; - if
is a question mark?
, then the -th character of the password might be any uppercase letters, lowercase letters, and digital characters.
It's guaranteed that the string ?
.
In addition, the system also has several requirements for passwords:
- At least one uppercase letter appears in the password;
- At least one lowercase letter appears in the password;
- At least one digital character appears in the password;
- Any two adjacent characters in the password cannot be the same.
Now you want to know, how many possible passwords are there? A possible password should fits both your memory and the system's requirements, and it’s guaranteed that there exists at least one possible password.
You know that this answer will be very large, so you just need to calculate the result modulo
Please note the unusual memory limit.
Input
The first line contains a single integer
The second line contains a string ?
.
It’s guaranteed that there exists at least one possible password.
Output
output a single line containing a single integer, representing the answer modulo
Sample
Input
4
a?0B
Output
86
题解
使用状态压缩 DP 思想。
状态表示
为了让字符能够用连续整型表示,将字符映射到整型,记这个映射为
- 大写 A ~ Z:
- 小写 a ~ z:
- 数字 0 ~ 9:
字符串状态
# | 是否含有大写 | 是否含有小写 | 是否含有数字 |
---|---|---|---|
0 | 0 | 0 | 0 |
1 | 0 | 0 | 1 |
2 | 0 | 1 | 0 |
3 | 0 | 1 | 1 |
4 | 1 | 0 | 0 |
5 | 1 | 0 | 1 |
6 | 1 | 1 | 0 |
7 | 1 | 1 | 1 |
状态计算
记输入字符串为
- 如果
为大写字符,对于最高位为 的 :
- 如果
为数字,对于最低位为 的 :
- 如果
为小写字符,对于中间位为 的 :
- 如果
为小写字符,记 为 对应的大写字符,对于最高位为 的 :
- 如果
为问号 ,则对于任意 :
- 对于其他情况,不进行转移。
初始化
- 若
为大写字符: - 若
为小写字符,其对应的大写字符为 : - 若
为数字: - 若
为问号 : ,其中 , 是对应 的状态
空间优化
上面所诉算法的空间复杂度为
最大情况 dp 需要储存
因此,选择滚动数组进行储存,这样只需要
时间优化
上面所诉算法最好为
为了优化问号情况的计算速度,预先求和:
那么计算问号时,无需再每次重复求和,直接取值就行,可以优化掉一个循环。
优化后,时间复杂度稳定为
神秘问题
本题不要尝试使用 map/unordered_map 储存字符映射,因为每次转移都会查询字符映射,最多查询约
代码
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
constexpr int MOD = 998244353;
int dp[2][62][8];
int sum[8];
/* # upper lower num
* 0 0 0 0
* 1 0 0 1
* 2 0 1 0
* 3 0 1 1
* 4 1 0 0
* 5 1 0 1
* 6 1 1 0
* 7 1 1 1
*/
int mp[128];
void init()
{
for (int i = 0; i < 26; i++)
{
mp['a' + i] = i;
mp['A' + i] = i + 26;
}
for (int i = 0; i < 10; i++)
{
mp['0' + i] = i + 52;
}
}
void solve()
{
int n;
cin >> n;
string s;
cin >> s;
// init dp
char &frt = s[0];
if (isupper(frt))
{
dp[0][mp[frt]][4] = 1;
}
else if (islower(frt))
{
dp[0][mp[frt]][2] = 1;
dp[0][mp[toupper(frt)]][4] = 1;
}
else if (isdigit(frt))
{
dp[0][mp[frt]][1] = 1;
}
else // if (frt == '?')
{
for (int i = 0; i < 26; i++)
dp[0][i][2] = 1;
for (int i = 26; i < 52; i++)
dp[0][i][4] = 1;
for (int i = 52; i < 62; i++)
dp[0][i][1] = 1;
}
for (int i = 0; i < 8; i++)
{
sum[i] = 0;
for (int j = 0; j < 62; j++)
{
sum[i] += dp[0][j][i];
sum[i] %= MOD;
}
}
int op = 1;
for (int i = 2; i <= n; i++, op ^= 1)
{
char &now = s[i - 1];
for (int j = 0; j < 62; j++)
{
for (int k = 0; k < 8; k++)
{
dp[op][j][k] = 0;
}
}
if (isupper(now))
{
for (int j = 0; j < 62; j++)
{
if (j == mp[now])
continue;
for (int k = 0; k < 8; k++)
{
if (k >> 2 & 1) // 1**
{
dp[op][mp[now]][k] += dp[op ^ 1][j][k];
dp[op][mp[now]][k] += dp[op ^ 1][j][k ^ (1 << 2)];
dp[op][mp[now]][k] %= MOD;
}
}
}
}
else if (isdigit(now))
{
for (int j = 0; j < 62; j++)
{
if (j == mp[now])
continue;
for (int k = 0; k < 8; k++)
{
if (k >> 0 & 1) // **1
{
dp[op][mp[now]][k] += dp[op ^ 1][j][k];
dp[op][mp[now]][k] += dp[op ^ 1][j][k ^ (1 << 0)];
dp[op][mp[now]][k] %= MOD;
}
}
}
}
else if (islower(now))
{
for (int j = 0; j < 62; j++)
{
if (j == mp[now])
continue;
for (int k = 0; k < 8; k++)
{
if (k >> 1 & 1) // *1*
{
dp[op][mp[now]][k] += dp[op ^ 1][j][k];
dp[op][mp[now]][k] += dp[op ^ 1][j][k ^ (1 << 1)];
dp[op][mp[now]][k] %= MOD;
}
}
}
for (int j = 0; j < 62; j++)
{
if (j == mp[toupper(now)])
continue;
for (int k = 0; k < 8; k++)
{
if (k >> 2 & 1) // 1**
{
dp[op][mp[toupper(now)]][k] += dp[op ^ 1][j][k];
dp[op][mp[toupper(now)]][k] += dp[op ^ 1][j][k ^ (1 << 2)];
dp[op][mp[toupper(now)]][k] %= MOD;
}
}
}
}
else // if (now == '?')
{
for (int j = 0; j < 26; j++)
{
for (int k = 0; k < 8; k++)
{
if (k >> 1 & 1) // *1*
{
dp[op][j][k] += sum[k];
dp[op][j][k] -= dp[op ^ 1][j][k];
dp[op][j][k] += sum[k ^ (1 << 1)];
dp[op][j][k] -= dp[op ^ 1][j][k ^ (1 << 1)];
dp[op][j][k] %= MOD;
dp[op][j][k] += MOD;
dp[op][j][k] %= MOD;
}
}
}
for (int j = 26; j < 52; j++)
{
for (int k = 0; k < 8; k++)
{
if (k >> 2 & 1) // 1**
{
dp[op][j][k] += sum[k];
dp[op][j][k] -= dp[op ^ 1][j][k];
dp[op][j][k] += sum[k ^ (1 << 2)];
dp[op][j][k] -= dp[op ^ 1][j][k ^ (1 << 2)];
dp[op][j][k] %= MOD;
dp[op][j][k] += MOD;
dp[op][j][k] %= MOD;
}
}
}
for (int j = 52; j < 62; j++)
{
for (int k = 0; k < 8; k++)
{
if (k >> 0 & 1) // **1
{
dp[op][j][k] += sum[k];
dp[op][j][k] -= dp[op ^ 1][j][k];
dp[op][j][k] += sum[k ^ (1 << 0)];
dp[op][j][k] -= dp[op ^ 1][j][k ^ (1 << 0)];
dp[op][j][k] %= MOD;
dp[op][j][k] += MOD;
dp[op][j][k] %= MOD;
}
}
}
}
for (int j = 0; j < 8; j++)
{
sum[j] = 0;
for (int k = 0; k < 62; k++)
{
sum[j] += dp[op][k][j];
sum[j] %= MOD;
}
}
}
int ans = 0;
for (int i = 0; i < 62; i++)
ans = (ans + dp[op ^ 1][i][7]) % MOD;
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t = 1;
// cin >> t;
init();
while (t--)
solve();
return 0;
}
本文采用 CC BY-SA 4.0 许可,本文 Markdown 源码:Haotian-BiJi