AtCoder Beginner Contest 468 ABCDE
A - Maximal Value
- 预估难度:
入门 - 标签:一维数组、模拟
题意
给定一个长度为 \(N\) 的整数序列:\(A=(A_1,A_2,\ldots,A_N)\)。
请找出有多少个整数 \(i\) 满足 \(1 \leq i \leq N-2\) 且 \(A_i \lt A_{i+1} \gt A_{i+2}\)。
数据范围
- \(3\le N\le 100\)
- \(1\le A_i\le 100\)
代码
void solve()
{
int n, a[105];
cin >> n;
for(int i = 1; i <= n; i++)
cin >> a[i];
int cnt = 0;
for(int i = 1; i <= n - 2; i++)
if(a[i] < a[i + 1] && a[i + 1] > a[i + 2])
cnt++;
cout << cnt;
}
B - Corridor Watch
- 预估难度:
入门 - 标签:计数思想
题意
给定整数 \(M\)、\(D\),以及一个长度为 \(M\) 的字符串 \(S\),字符串仅由字符 G 和 . 组成。
有 \(M\) 个格子排成一行,从左到右依次编号为 \(1\) 到 \(M\)。
部分格子上站有守卫。具体来说,如果 \(S_i=\) G,则第 \(i\) 个格子上站有一名守卫;如果 \(S_i=\) .,则第 \(i\) 个格子上没有守卫。
如果某个格子与有守卫的格子之间的距离不超过 \(D\),则该格子会被该守卫监视。也就是说,如果存在一个格子 \(i\) 满足 \(S_i=\) G 且 \(|x-i|\le D\),则格子 \(x\) 会被守卫监视。
在这 \(M\) 个格子中,请找出未被监视的格子数量。
数据范围
- \(0\le D < M \le 100\)
- \(S_i\) 是一个长度为 \(M\) 且仅由字符
G和.组成的字符串
思路
借助计数数组 vis[i] 用来标记格子 \(i\) 是否被某个守卫者监视。
如果 $S_i = $ G,说明存在一名守卫者能够管辖区间 \([i-D, i+D]\) 范围内的所有格子。
由于格子编号仅从 \(1\) 到 \(M\) 过,因此实际管辖的区间左端点为 \(\max(i-D, 1)\),右端点为 \(\min(i+D, M)\)。
将这段区间内的所有格子标记为被监视,最后统计有多少个格子没有被监视即可。
代码
bool vis[105];
// vis[i] 表示 i 这个格子是否被某个守卫者监视
void solve()
{
int n, d;
cin >> n >> d;
for(int i = 1; i <= n; i++)
{
char c;
cin >> c;
if(c == 'G')
{
int l = max(1, i - d);
int r = min(n, i + d);
for(int j = l; j <= r; j++)
vis[j] = true; // 标记这一段区间内的所有位置都会被监视
}
}
int cnt = 0;
for(int i = 1; i <= n; i++)
if(!vis[i]) // 数没有被监视的格子数量
cnt++;
cout << cnt;
}
C - Between P and Q
- 预估难度:
普及- - 标签:STL函数 / 深度优先搜索
题意
给定一个整数 \(N\),以及两个整数序列 \(P=(P_1,P_2,\ldots, P_N)\) 和 \(Q=(Q_1,Q_2,\ldots,Q_N)\),它们均为 \((1,2,\ldots,N)\) 的排列。
请求出有多少个整数序列,满足它是 \((1,2,\ldots,N)\) 的排列,且在字典序上严格大于 \(P\) 且严格小于 \(Q\)。
数据范围
- \(1\le N\le 10\)
思路
注意到 \(N \le 10\),并且 \(N\) 的全排列数量共 \(N!\) 种,因此可以考虑找出所有 \(N\) 的全排列再一一比较。
找全排列的方式有很多,一般做法为深度优先搜索或是借助 STL 函数 next_permutation 进行。
若是借助 next_permutation,一种更简便的方法是,我们可以直接从给定的序列 \(P\) 开始向后不断构造下一个字典序的排列,此时得到的序列一定严格大于原本的 \(P\) 序列,只需要判断是否严格小于 \(Q\) 序列即可。
时间复杂度 \(O(N\cdot N!)\)。
代码
int n, p[15], q[15];
// 判断目前 p 序列字典序是否小于 q 序列
bool check()
{
for(int i = 1; i <= n; i++)
if(p[i] != q[i])
return p[i] < q[i]; // 直接判断对应数字关系
return false; // 说明两序列相同
}
void solve()
{
cin >> n;
for(int i = 1; i <= n; i++)
cin >> p[i];
for(int i = 1; i <= n; i++)
cin >> q[i];
int cnt = 0;
// 先向后构造下一个字典序的排列,再判断 p 序列字典序是否小于 q 序列
while(next_permutation(p + 1, p + n + 1) && check())
cnt++;
cout << cnt;
}
D - Pre-Palindrome
- 预估难度:
普及 - 标签:枚举
题意
如果一个仅由小写英文字母组成的字符串满足以下条件,则称其为好字符串:
- 通过最多修改其中一个字符,可以将其变为回文串。
例如,a、iwai 和 abcdcza 是好字符串,但 abcd 和 atcoder 不是好字符串。请注意,回文串本身也是好字符串。
给定一个仅由英文小写字母组成的字符串 \(S\),请计算 \(S\) 中有多少个非空子串(连续子序列)是好字符串。
即使从 \(S\) 的不同位置取出的两个子串作为字符串是相等的,它们也会被分别计数。
数据范围
- \(S\) 的长度在 \([1, 10^4]\) 范围内,且仅由小写英文字母组成
思路
对于本题,判断一个字符串是否是好字符串,可以直接用类似于判断回文串的方法,判断该字符串对称位置的字符不同的情况是否最多只出现一处。
考虑统计。如果通过常规方法,先 \(O(N^2)\) 枚举子串的左右端点,再 \(O(N)\) 检查回文串是否是好字符串,这个 \(O(N^3)\) 的复杂度是不可接受的。
但我们可以根据回文串的性质转换枚举的角度。由于回文串是中心对称的,如果我们能先 \(O(N)\) 枚举字符串的中点,然后从中点开始向两边同时扩张出去,一边 \(O(N)\) 枚举左右边界,一边判断对称位置的字符是否相同,就可以实现 \(O(N^2)\) 的统计了。
最后注意分奇偶性讨论回文串中点。
- 没接触过这题的同学可以自行再做一做
[USACO 2025 January Bronze] Cow Checkups这道题。
代码
void solve()
{
string s;
cin >> s;
int n = s.size(), cnt = 0;
// 假设回文串长度为奇数,枚举中点 i
for(int i = 0; i < n; i++)
{
int l = i, r = i, diff = 0;
// l, r 分别记录目前回文串的左右端点
// diff 记录已经出现了多少个位置对应字符不同
while(l >= 0 && r < n)
{
if(s[l] != s[r])
diff++;
if(diff <= 1) // 只要对称位置不同的数量还没超过 1 个,就还符合条件
cnt++;
l--; // 向两边扩张出去枚举
r++;
}
}
// 假设回文串长度为偶数,枚举中点为 i 和 i+1 之间的位置
for(int i = 0; i < n - 1; i++)
{
int l = i, r = i + 1, diff = 0;
while(l >= 0 && r < n)
{
if(s[l] != s[r])
diff++;
if(diff <= 1)
cnt++;
l--;
r++;
}
}
cout << cnt;
}
E - Sum of Average
- 预估难度:
普及+/提高- - 标签:枚举、数学、逆元、(二维)前缀和
题意
给定一个正整数 \(N\) 和一个长度为 \(N\) 的整数序列 \(A=(A_1,A_2,\ldots,A_N)\)。
定义 \(f(l,r)\) 为 \(A_l,A_{l+1},\ldots,A_r\) 的算术平均数。
请计算 \(\displaystyle \sum_{1\le l\le r\le N} f(l,r)\) 的值,结果对 \(998244353\) 取模。
数据范围
- \(1\le N\le 5\times 10^5\)
- \(0\le A_i \lt 998244353\)
思路
注意到区间算术平方数等于区间总和除以区间长度。
我们可以借助一维前缀和,记 \(S_i = A_1 + A_2 + \dots + A_i\),来将求区间总和这一步优化至 \(O(1)\)。
那么 \(f(l, r) = \dfrac{S_r - S_{l-1}}{r - l + 1}\)
但这样也还需要 \(O(N^2)\) 枚举区间的左右端点,时间复杂度不可接受。
考虑将视角转向枚举区间长度 \(L\),当长度确定时,算术平均数的除数部分就固定了。
考虑被除数部分,可以写成所有长度为 \(L\) 的区间总和:
于是我们再对前缀和数组做一次前缀和,记 \(P_i = S_1 + S_2 + \ldots + S_i\),那么上式便可以写成:
那么长度为 \(L\) 的所有区间的算术平均数对答案的贡献即:
最终枚举长度+计算的时间复杂度 \(O(N)\),预处理 \(1 \sim N\) 每个整数的逆元时间复杂度 \(O(N)\)(公式法)或 \(O(N + \log N)\) (借助阶乘与阶乘逆元推导)。
代码
typedef long long ll;
ll qpow(ll a, ll n) // 快速幂
{
ll r = 1;
while(n)
{
if(n & 1)
r = r * a % mod;
a = a * a % mod;
n >>= 1;
}
}
ll fac[500005], inv[500005], d[500005];
// fac[i] 表示 i! (%mod)
// inv[i] 表示 1/i! (%mod)
// d[i] 表示 1/i (%mod)
void init(int N)
{
fac[0] = 1;
for(int i = 1; i <= N; i++)
fac[i] = fac[i - 1] * i % mod;
inv[N] = qpow(fac[N], mod - 2);
for(int i = N - 1; i >= 0; i--)
inv[i] = inv[i + 1] * (i + 1) % mod;
// 1 / i == (i-1)! / i!
for(int i = 1; i <= N; i++)
d[i] = fac[i - 1] * inv[i] % mod;
}
int A[500005];
ll S[500005]; // S[i] 表示 A[1 ... i] 的总和
ll P[500005]; // P[i] 表示 S[1 ... i] 的总和
void solve()
{
int N;
cin >> N;
init(N);
for(int i = 1; i <= N; i++)
{
cin >> A[i];
S[i] = (S[i - 1] + A[i]) % mod;
}
for(int i = 1; i <= N; i++)
P[i] = (P[i - 1] + S[i]) % mod;
ll ans = 0;
for(int L = 1; L <= N; L++) // 枚举区间长度
{
ll sum = (P[N] - P[N - L]) - P[L - 1];
sum = (sum % mod + mod) % mod;
ans = (ans + sum * d[L]) % mod;
}
cout << ans;
}

浙公网安备 33010602011771号