AtCoder Beginner Contest 452 ABCDE 题目解析
A - Gothec
- 预估难度:
入门
题意
判断给定的年月是否是一月七日、三月三日、五月五日、七月七日以及九月九日这五天之一。
代码
void solve()
{
int m, d;
cin >> m >> d;
if(m == 1 && d == 7
|| m == 3 && d == 3
|| m == 5 && d == 5
|| m == 7 && d == 7
|| m == 9 && d == 9)
cout << "Yes";
else
cout << "No";
}
B - Draw Frame
- 预估难度:
入门
题意
画一个 \(H \times W\) 的矩形网格图,只有边框上是 #,中间的位置均为 .。
思路
循环嵌套输出,判断当前所在行、列是否是第一行/列或者最后一行/列即可。
代码
void solve()
{
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
if(i == 1 || j == 1 || i == n || j == m) // 边框
cout << "#";
else
cout << ".";
}
cout << "\n";
}
}
C - Fishbones
- 预估难度:
普及- - 标签:计数思想、字符串
题意
给定 \(N\) 对数对 \(A_i, B_i\) 以及 \(M\) 个互不相同的字符串 \(S_1, S_2, \dots, S_M\)。
如果一个字符串 \(T\) 要作为脊柱使用,则需要满足以下条件:
- 字符串 \(T\) 的长度为 \(N\)。
- 存在一种方法,能从给定的 \(M\) 个字符串中选出 \(N\) 个字符串(允许重复选择),并且对于选出的第 \(i\) 个字符串,其长度必须是 \(A_i\),且其中第 \(B_i\) 个字符必须和字符串 \(T\) 的第 \(i\) 个字符相同。
对于给定的这 \(M\) 个字符串,请分别判断每个字符串是否能够作为脊柱使用。
思路
首先排除所有长度不等于 \(N\) 的字符串。
然后考虑题中第二个条件,因为允许重复选择,所以就等同于判断“是否存在某个字符串长度为 \(A_i\) 且第 \(B_i\) 个字符是等待判断的字符串的第 \(i\) 个字符”。
可以想到借助三维计数数组辅助进行判断,用 vis[i][j][k] 标记是否存在某个字符串长度为 \(i\),且第 \(j\) 个字符恰好为 \(k\)。
输入的时候便可以更新该计数数组,之后只需要对于每个给定的字符串分别判断一次即可。
时间复杂度 \(O(M\cdot|S|)\)。
代码
int n, m, a[15], b[15];
string s[200005];
bool vis[12][12][128];
// vis[i][j][k] 表示是否存在某个字符串,长度是 i,且第 j 个字符 ASCII 恰好是 k
void solve()
{
cin >> n;
for(int i = 1; i <= n; i++)
cin >> a[i] >> b[i];
cin >> m;
for(int i = 1; i <= m; i++)
{
cin >> s[i];
int len = s[i].size();
for(int j = 1; j <= len; j++)
{
// 该字符串的第 j 个字为 s[i][j-1]
vis[len][j][s[i][j-1]] = true;
}
}
for(int i = 1; i <= m; i++)
{
if(s[i].size() != n)
{
cout << "No\n";
continue;
}
bool flag = true;
for(int j = 1; j <= s[i].size(); j++)
{
// 判断是否存在某个字符串 长度为 a[j]
// 且第 b[j] 个字与当前字符串的第 j 个字相同
if(!vis[a[j]][b[j]][s[i][j-1]])
{
flag = false;
break;
}
}
if(flag)
cout << "Yes\n";
else
cout << "No\n";
}
}
D - No-Subsequence Substring
- 预估难度:
普及/提高- - 标签:枚举、计数思想、前缀思想、双指针
题意
给定仅由小写字母组成的字符串 \(S, T\)。
问在 \(S\) 的所有非空子串中,有多少个子串满足以下条件:
- \(T\) 不是该子串的子序列。
思路
可以考虑对于 \(S\) 字符串,枚举每个位置 \(i\) 作为左端点,然后再考虑统计有多少个右端点符合条件。
在左端点固定为 \(i\) 的情况下,如果右端点设为 \(j\) 时导致 \(T\) 成为了 \(S[i \cdots j]\) 的子序列,那么所有 \(\ge j\) 的下标就都不能当作子串的右端点。
换句话说,我们需要找到最大的一个下标 \(j\),满足 \(T\) 仍然不是 \(S[i \cdots j]\) 的子序列。
一般来说,判断一个序列 \(X\) 是否是序列 \(Y\) 的子序列,我们可以借助双指针的方法,按顺序枚举 \(X\) 中的每一个元素 \(c\),然后来一个新的指针 \(j\) 在序列 \(Y\) 中每次向后找下一个 \(c\)。如果存在,取最靠前的一个 \(c\) 当作选择的元素,这样可以保证有更多的可能性继续选择更后面的元素作为子序列的一部分。
但本题的序列是字符串,且仅由小写字母组成,因此我们可以先对字符串 \(S\) 预处理一个数组 nxt[i][j],表示从 \(i\) 位置开始往后第一次出现字符 \(j\) 时的位置。
那么,我们就可以从枚举的左端点位置 \(i\) 开始,按 \(T\) 字符串的顺序,每次从上一个字符的位置开始,向后继续找下一个字符出现的第一个位置。
如果过程中发现某个字符不出现了,就说明以 \(i\) 作为子串左端点位置时,不可能存在某个子序列为 \(T\)。
而如果在看完 \(T\) 字符串的最后一个字符后,仍然能找到对应的字符所在位置,记该位置为 \(p\),那么此时符合条件的子串右端点必须 \(\lt p\)。
由于字符串 \(T\) 长度较短,因此 \(O(|S|)\) 枚举 \(S\) 字符串的左端点后,每次暴力向后跳即可。
时间复杂度 \(O(|S| \cdot (\sigma + |T|))\),其中 \(\sigma = 26\)。
代码
string s, t;
int nxt[200005][26];
// nxt[i][j] 表示 s 字符串中下标 i 之后第一次出现字符 j 的位置
void solve()
{
cin >> s >> t;
int n = s.size(), m = t.size();
s = " " + s;
t = " " + t; // 下标从 1 开始
for(int i = n; i >= 1; i--)
{
// 继承后一个位置的答案
for(int j = 'a'; j <= 'z'; j++)
nxt[i][j] = nxt[i + 1][j];
// 当前字符是 s[i],说明下一次出现 s[i] 就是当前位置
nxt[i][s[i]] = i;
}
long long ans = 0;
for(int i = 1; i <= n; i++)
{
int p = i - 1;
for(int j = 1; j <= m; j++)
{
p = nxt[p + 1][t[j]]; // 从 p+1 开始往后找下一次出现 t[j] 的位置
if(p == 0)
break;
}
if(p == 0) // 说明以 i 为左端点时,所有子串均符合条件
ans += n - i + 1; // 右端点区间 [i, n]
else // 说明以 i 为左端点时,如果右端点 >= p,则会存在 t 作为子序列
ans += p - i; // 右端点区间 [i, p-1]
}
cout << ans;
}
E - You WILL Like Sigma Problem
- 预估难度:
普及+/提高 - 标签:数学、前缀和
题意
给定长度为 \(N\) 的正整数序列 \(A = (A_1, \cdots, A_N)\) 和长度为 \(M\) 的正整数序列 \(B = (B_1, \cdots, B_M)\)。
求 \(\displaystyle \sum_{i=1}^{N} \sum_{j=1}^{M} A_i \cdot B_j \cdot (i \bmod j)\) 的值,对 \(998244353\) 取模。
思路
整理公式:
对于被减数部分,可以分别求出 \(\displaystyle\sum_{i=1}^{N} (A_i \cdot i)\) 以及 \(\displaystyle\sum_{j=1}^{M} B_j\) 后相乘得到,时间复杂度 \(O(N+M)\)。
对于减数部分,考虑枚举 \(j = 1 \sim M\),然后重点解决 \(\displaystyle\sum_{i=1}^{N} (A_i \cdot \lfloor \frac i j \rfloor)\) 这一项。
当 \(j\) 固定时,可以发现当 \(k \times j \le i \lt (k+1)\times j\) 时(\(k\) 为正整数),\(\displaystyle\lfloor \frac i j \rfloor\) 的答案固定为 \(k\)。因此我们可以考虑直接枚举整除所得到的商 \(k\),那么:
对于 \(\displaystyle \sum_{i = k \times j}^{\min(N, (k+1)\times j-1)} A_i\),在计算出左右端点后可以借助前缀和思想快速求得。
对于 \(\displaystyle \sum_{k = 1}^{\lfloor \frac N j \rfloor}\) 的枚举,该部分总执行次数为 \(\displaystyle \sum_{j=1}^M \lfloor \frac N j \rfloor \approx N\log M\)。
总时间复杂度 \(O(N\log M)\)。
代码
typedef long long ll;
const ll mod = 998244353;
int n, m;
ll a[500005], b[500005];
ll s[500005]; // a[] 的前缀和
void solve()
{
cin >> n >> m;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
s[i] = (s[i - 1] + a[i]) % mod;
}
for(int i = 1; i <= m; i++)
cin >> b[i];
ll t1 = 0, t2 = 0;
for(int i = 1; i <= n; i++)
t1 = (t1 + a[i] * i) % mod;
for(int i = 1; i <= m; i++)
t2 = (t2 + b[i]) % mod;
ll ans = t1 * t2 % mod;
for(int j = 1; j <= m; j++)
{
ll f = 0;
for(int k = 1; k <= n / j; k++) // 枚举整除的商
{
int l = k * j, r = min(n, (k + 1) * j - 1);
// [l, r] 这段区间所有整数 / j 的商均为 k
ll sum = (s[r] - s[l - 1] + mod) % mod;
f = (f + k * sum) % mod;
}
ans = (ans - j * b[j] * f % mod % mod + mod) % mod;
}
cout << ans;
}

浙公网安备 33010602011771号