1. AtCoder Beginner Contest 442 全题解
最近参加了AtCoder Beginner Contest 442,感觉题目整体难度适中,特别是前几题非常适合算法初学者练习。本文将详细解析A-F六道题目的解题思路和代码实现,希望能帮助到正在准备编程竞赛的朋友们。
2. A题 - Count .
2.1 题目分析
题目要求统计给定字符串中'i'和'j'字符的出现次数。这是一道非常基础的字符串处理题目,主要考察对字符串的遍历和条件判断能力。
2.2 解题思路
- 读取输入的字符串
- 初始化计数器为0
- 遍历字符串中的每个字符
- 如果当前字符是'i'或'j',计数器加1
- 输出最终计数结果
2.3 代码实现
cpp复制#include <bits/stdc++.h>
using namespace std;
void solve() {
string s;
cin >> s;
int ans = 0;
for(auto ch : s) {
if(ch == 'i' || ch == 'j') {
ans++;
}
}
cout << ans << endl;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
solve();
return 0;
}
2.4 注意事项
- 注意字符串可能包含空格或其他特殊字符,但题目保证只包含小写字母
- 大小写敏感,题目要求统计的是小写'i'和'j'
- 时间复杂度O(n),空间复杂度O(1),n为字符串长度
3. B题 - Music Player
3.1 题目分析
模拟一个音乐播放器的音量控制逻辑:
- 操作1:音量+1
- 操作2:音量-1(不低于0)
- 操作3:切换播放/暂停状态
当播放状态且音量≥3时输出"Yes",否则输出"No"
3.2 解题思路
- 维护两个状态变量:当前音量level和播放状态stop
- 根据输入的操作类型更新状态
- 每次操作后根据条件输出相应结果
3.3 代码实现
cpp复制#include <bits/stdc++.h>
using namespace std;
void solve() {
int q;
cin >> q;
int level = 0;
bool stop = true;
while(q--) {
int op;
cin >> op;
if(op == 1) {
level++;
} else if(op == 2) {
level = max(level - 1, 0);
} else {
stop = !stop;
}
if(!stop && level >= 3) {
cout << "Yes\n";
} else {
cout << "No\n";
}
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
solve();
return 0;
}
3.4 常见问题
- 注意音量不能为负数,使用max函数确保最小值
- 播放状态使用布尔变量更直观
- 每次操作后都要检查输出条件
4. C题 - Peer Review
4.1 题目分析
给定n个人和m对互评关系,要求计算每个人可以组成多少个三人评审小组(自己+两个未互评过的人)。
4.2 解题思路
- 统计每个人的互评次数cnts[i]
- 对于每个人i,可选人数为n - cnts[i] - 1(减去自己和互评过的人)
- 计算组合数C(k,3),其中k为可选人数
- 如果k<3,结果为0
4.3 代码实现
cpp复制#include <bits/stdc++.h>
using namespace std;
void solve() {
int n, m;
cin >> n >> m;
vector<long long> cnts(n + 1);
for(int i = 0; i < m; i++) {
int x, y;
cin >> x >> y;
cnts[x]++;
cnts[y]++;
}
for(int i = 1; i <= n; i++) {
long long k = n - cnts[i] - 1;
if(k < 3) {
cout << "0 ";
} else {
cout << k * (k - 1) * (k - 2) / 6 << " ";
}
}
cout << endl;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
solve();
return 0;
}
4.4 注意事项
- 组合数计算可能溢出,使用long long类型
- 注意数组从1开始编号
- 时间复杂度O(n+m)
5. D题 - Swap and Range Sum
5.1 题目分析
给定一个数组,支持两种操作:
- 交换相邻元素
- 查询区间和
5.2 解题思路
5.2.1 线段树解法
可以直接套用线段树模板,但效率不高:
- 交换操作:两次单点更新
- 查询操作:区间查询
5.2.2 优化解法
观察发现交换相邻元素对前缀和的影响有限:
- 只有交换位置的前缀和会改变
- 维护动态前缀和数组
- 交换时只需更新一个位置的前缀和
5.3 代码实现
cpp复制#include <bits/stdc++.h>
using namespace std;
void solve() {
int n, q;
cin >> n >> q;
vector<long long> a(n + 1), pre(n + 1);
for(int i = 1; i <= n; i++) {
cin >> a[i];
pre[i] = pre[i - 1] + a[i];
}
while(q--) {
int op;
cin >> op;
if(op == 1) {
int x;
cin >> x;
swap(a[x], a[x + 1]);
pre[x] = pre[x - 1] + a[x];
} else {
int l, r;
cin >> l >> r;
cout << pre[r] - pre[l - 1] << "\n";
}
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
solve();
return 0;
}
5.4 性能分析
- 线段树解法:每次操作O(logn)
- 前缀和解法:交换O(1),查询O(1)
- 实际测试前缀和解法明显更快
6. E题 - Laser Takahashi
6.1 题目分析
给定平面上的n个点,回答q个查询:从原点看,点a和点b之间(顺时针方向)有多少个点。
6.2 解题思路
- 极角排序:按顺时针方向排序所有点
- 预处理每个点的排名和相同极角的区间
- 对于查询a和b:
- 如果a在b顺时针方向:R[b]-L[a]+1
- 否则:n - (L[a]-R[b]-1)
6.3 代码实现
cpp复制#include <bits/stdc++.h>
using namespace std;
long long cross(const array<long long, 2>& a, const array<long long, 2>& b) {
return a[0] * b[1] - a[1] * b[0];
}
void solve() {
int n, q;
cin >> n >> q;
vector<array<long long, 2>> loc(n + 1);
for(int i = 1; i <= n; i++) {
cin >> loc[i][0] >> loc[i][1];
}
vector<int> id(n + 1);
for(int i = 1; i <= n; i++) id[i] = i;
auto cmp = [&](int x, int y) {
int a = loc[x][1] < 0 || (loc[x][1] == 0 && loc[x][0] < 0);
int b = loc[y][1] < 0 || (loc[y][1] == 0 && loc[y][0] < 0);
if(a != b) return a < b;
return cross(loc[x], loc[y]) > 0;
};
sort(id.begin() + 1, id.end(), cmp);
reverse(id.begin() + 1, id.end());
vector<int> rnk(n + 1);
for(int i = 1; i <= n; i++) {
rnk[id[i]] = i;
}
vector<int> L(n + 1), R(n + 1);
L[1] = 1;
for(int i = 2; i <= n; i++) {
L[i] = (cmp(id[i], id[i - 1]) ? i : L[i - 1]);
}
R[n] = n;
for(int i = n - 1; i >= 1; i--) {
R[i] = (cmp(id[i + 1], id[i]) ? i : R[i + 1]);
}
while(q--) {
int a, b;
cin >> a >> b;
a = rnk[a], b = rnk[b];
if(L[a] <= R[b]) {
cout << R[b] - L[a] + 1 << "\n";
} else {
cout << n - (L[a] - R[b] - 1) << "\n";
}
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
solve();
return 0;
}
6.4 几何知识
- 叉积判断向量相对位置
- 极角排序处理平面点序
- 注意处理共线情况
7. F题 - Diagonal Separation 2
7.1 题目分析
给定n×n的网格,每个格子是黑或白。通过最少的操作将网格分成左上全白和右下全黑两部分,每次操作可以翻转整行或整列的颜色。
7.2 解题思路
动态规划:
- dp[i][j]:处理到第i行,分界线在第j列时的最小操作数
- 转移:dp[i][j] = min(dp[i-1][k] for k >= j) + 当前行代价
- 使用后缀最小值优化
7.3 代码实现
cpp复制#include <bits/stdc++.h>
using namespace std;
void solve() {
int n;
cin >> n;
vector<vector<int>> g(n + 1, vector<int>(n + 1));
for(int i = 1; i <= n; i++) {
string s;
cin >> s;
for(int j = 1; j <= n; j++) {
g[i][j] = (s[j - 1] == '#');
}
}
vector<vector<int>> suf(n + 1, vector<int>(n + 2));
for(int i = 1; i <= n; i++) {
for(int j = n; j >= 1; j--) {
suf[i][j] = suf[i][j + 1] + g[i][j];
}
}
vector<int> up(n + 1, 0);
for(int i = 1; i <= n; i++) {
vector<int> dp(n + 1);
int pre = 0;
for(int j = 0; j <= n; j++) {
pre += g[i][j];
dp[j] = pre + (n - j - suf[i][j + 1]) + up[j];
}
int cur = INT_MAX;
for(int j = n; j >= 0; j--) {
cur = min(cur, dp[j]);
up[j] = cur;
}
}
cout << up[0] << endl;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
solve();
return 0;
}
7.4 优化技巧
- 预处理每行后缀黑格子数
- 使用滚动数组优化空间
- 后缀最小值预处理加速转移
8. 参赛总结
这次比赛题目质量很高,覆盖了多种算法题型:
- A-B题:基础编程能力考察
- C题:组合数学应用
- D题:数据结构与算法优化
- E题:计算几何知识
- F题:动态规划与优化
在实际编程竞赛中,有几点经验值得分享:
- 先解决简单题确保基础分
- 对于数据结构的题目,先思考是否有更优解法
- 几何题要注意特殊情况和精度处理
- DP题目要明确状态定义和转移方程
- 合理分配时间,避免卡在一道题上太久
这次比赛能够解决所有题目,说明平时的算法训练是有效果的。建议初学者可以从AtCoder的Beginner Contest开始练习,逐步提升自己的算法能力。