
在IO竞赛的学习路径中,基础级难度的题目是连接入门和提高的重要桥梁。2025年的IO竞赛基础级(难度系数4-5)题目开始涉及更多的数据结构和算法思想,对选手的编程能力和逻辑思维提出了更高的要求。本文将详细解析2025年基础级的IO竞赛题目,帮助选手们顺利过渡到更高难度的学习阶段。
难度进阶路径: 入门(1-3) → 基础(4-5) → 提高(6-8) → 竞赛(9-10)难度系数 | 考察重点 | 核心知识点 | 学习目标 |
|---|---|---|---|
4-5 | 数据结构、算法应用 | 栈、队列、树、图的基础应用 | 掌握基础数据结构的使用和简单算法的实现 |
目录
├── 第一章:2025年IO竞赛基础级题目概述
├── 第二章:难度系数4题目解析(8题)
├── 第三章:难度系数5题目解析(8题)
├── 第四章:基础级题目解题技巧总结
└── 第五章:从基础到提高的学习建议根据2025年NOI修订版大纲,基础级(CSP-J提高)的知识点难度系数为4-5,开始涉及更多的数据结构和算法应用。这些题目不仅要求选手掌握基本的编程语法,还需要理解和应用一些基础的数据结构和算法思想。
基础级题目类型分布:
数据结构应用 → 40%
算法设计 → 30%
数学应用 → 20%
综合问题 → 10%基础级题目的特点:
难度系数4的题目开始考察基础数据结构的应用和简单的算法设计。以下是8道典型的难度系数4题目解析。
题目描述:输入一个包含括号的字符串,判断括号是否完全匹配。
解题思路:使用栈来解决括号匹配问题。遍历字符串,遇到左括号则入栈,遇到右括号则检查栈顶元素是否匹配。
C++代码实现:
#include <iostream>
#include <stack>
#include <string>
using namespace std;
bool is_valid(string s) {
stack<char> st;
for (char c : s) {
if (c == '(' || c == '[' || c == '{') {
st.push(c);
} else {
if (st.empty()) return false;
char top = st.top();
st.pop();
if ((c == ')' && top != '(') ||
(c == ']' && top != '[') ||
(c == '}' && top != '{')) {
return false;
}
}
}
return st.empty();
}
int main() {
string s;
cin >> s;
if (is_valid(s)) {
cout << "Valid" << endl;
} else {
cout << "Invalid" << endl;
}
return 0;
}题目描述:给定一个数组和一个窗口大小k,找出所有滑动窗口里的最大值。
解题思路:使用双端队列来维护当前窗口中的最大值。队列中存储的是元素的索引,而非元素值本身。
C++代码实现:
#include <iostream>
#include <vector>
#include <deque>
using namespace std;
vector<int> max_sliding_window(vector<int>& nums, int k) {
vector<int> result;
deque<int> dq; // 存储索引
for (int i = 0; i < nums.size(); i++) {
// 移除超出窗口范围的元素
while (!dq.empty() && dq.front() < i - k + 1) {
dq.pop_front();
}
// 移除比当前元素小的元素
while (!dq.empty() && nums[dq.back()] < nums[i]) {
dq.pop_back();
}
dq.push_back(i);
// 当窗口形成时开始记录结果
if (i >= k - 1) {
result.push_back(nums[dq.front()]);
}
}
return result;
}
int main() {
int n, k;
cin >> n >> k;
vector<int> nums(n);
for (int i = 0; i < n; i++) {
cin >> nums[i];
}
vector<int> result = max_sliding_window(nums, k);
for (int num : result) {
cout << num << " ";
}
cout << endl;
return 0;
}题目描述:给定一棵二叉树,按层序遍历的顺序输出各节点的值。
解题思路:使用队列来实现二叉树的层序遍历。
C++代码实现:
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
// 二叉树节点的定义
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
vector<vector<int>> level_order(TreeNode* root) {
vector<vector<int>> result;
if (!root) return result;
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
int level_size = q.size();
vector<int> current_level;
for (int i = 0; i < level_size; i++) {
TreeNode* node = q.front();
q.pop();
current_level.push_back(node->val);
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
result.push_back(current_level);
}
return result;
}
// 构建二叉树(简化版,这里假设输入是按层序遍历的数组,-1表示空节点)
TreeNode* build_tree(vector<int>& nums, int index) {
if (index >= nums.size() || nums[index] == -1) return NULL;
TreeNode* root = new TreeNode(nums[index]);
root->left = build_tree(nums, 2 * index + 1);
root->right = build_tree(nums, 2 * index + 2);
return root;
}
int main() {
int n;
cin >> n;
vector<int> nums(n);
for (int i = 0; i < n; i++) {
cin >> nums[i];
}
TreeNode* root = build_tree(nums, 0);
vector<vector<int>> result = level_order(root);
for (auto& level : result) {
for (int num : level) {
cout << num << " ";
}
cout << endl;
}
return 0;
}题目描述:实现快速排序算法,对数组进行排序。
解题思路:选择一个基准元素,将数组分为两部分,小于基准的元素放在左边,大于基准的元素放在右边,然后递归地对左右两部分进行排序。
C++代码实现:
#include <iostream>
#include <vector>
using namespace std;
int partition(vector<int>& nums, int left, int right) {
int pivot = nums[right];
int i = left - 1;
for (int j = left; j < right; j++) {
if (nums[j] <= pivot) {
i++;
swap(nums[i], nums[j]);
}
}
swap(nums[i + 1], nums[right]);
return i + 1;
}
void quick_sort(vector<int>& nums, int left, int right) {
if (left < right) {
int pivot_index = partition(nums, left, right);
quick_sort(nums, left, pivot_index - 1);
quick_sort(nums, pivot_index + 1, right);
}
}
int main() {
int n;
cin >> n;
vector<int> nums(n);
for (int i = 0; i < n; i++) {
cin >> nums[i];
}
quick_sort(nums, 0, n - 1);
for (int num : nums) {
cout << num << " ";
}
cout << endl;
return 0;
}题目描述:给定一个单链表,将其反转。
解题思路:使用三个指针(前驱、当前、后继)来实现链表的反转。
C++代码实现:
#include <iostream>
using namespace std;
// 单链表节点的定义
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
ListNode* reverse_list(ListNode* head) {
ListNode* prev = NULL;
ListNode* curr = head;
while (curr) {
ListNode* next_temp = curr->next;
curr->next = prev;
prev = curr;
curr = next_temp;
}
return prev;
}
// 构建单链表
ListNode* build_list(int n) {
if (n == 0) return NULL;
ListNode* head = new ListNode(0);
ListNode* tail = head;
for (int i = 0; i < n; i++) {
int val;
cin >> val;
tail->next = new ListNode(val);
tail = tail->next;
}
ListNode* temp = head;
head = head->next;
delete temp;
return head;
}
// 打印链表
void print_list(ListNode* head) {
while (head) {
cout << head->val << " ";
head = head->next;
}
cout << endl;
}
int main() {
int n;
cin >> n;
ListNode* head = build_list(n);
head = reverse_list(head);
print_list(head);
return 0;
}题目描述:给定一个排序数组和一个目标值,找到目标值在数组中的开始位置和结束位置。
解题思路:使用两次二分查找,分别找到目标值的第一个位置和最后一个位置。
C++代码实现:
#include <iostream>
#include <vector>
using namespace std;
vector<int> search_range(vector<int>& nums, int target) {
vector<int> result = {-1, -1};
if (nums.empty()) return result;
// 找第一个位置
int left = 0, right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid;
}
}
if (nums[left] != target) return result;
result[0] = left;
// 找最后一个位置
right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left + 1) / 2; // 注意这里的向上取整
if (nums[mid] > target) {
right = mid - 1;
} else {
left = mid;
}
}
result[1] = right;
return result;
}
int main() {
int n, target;
cin >> n >> target;
vector<int> nums(n);
for (int i = 0; i < n; i++) {
cin >> nums[i];
}
vector<int> result = search_range(nums, target);
cout << result[0] << " " << result[1] << endl;
return 0;
}题目描述:给定一个无向图,使用Kruskal算法找到其最小生成树。
解题思路:Kruskal算法的基本思想是按照边的权值从小到大排序,然后依次选择边,如果加入这条边不会形成环,则将其加入最小生成树。
C++代码实现:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 边的定义
struct Edge {
int u, v, weight;
Edge(int u, int v, int weight) : u(u), v(v), weight(weight) {}
bool operator<(const Edge& other) const {
return weight < other.weight;
}
};
// 并查集的实现
class UnionFind {
private:
vector<int> parent;
public:
UnionFind(int n) {
parent.resize(n);
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
void unite(int x, int y) {
parent[find(x)] = find(y);
}
};
int kruskal(vector<Edge>& edges, int n) {
sort(edges.begin(), edges.end());
UnionFind uf(n);
int total_weight = 0;
int count = 0;
for (const Edge& edge : edges) {
if (uf.find(edge.u) != uf.find(edge.v)) {
uf.unite(edge.u, edge.v);
total_weight += edge.weight;
count++;
if (count == n - 1) break; // 最小生成树有n-1条边
}
}
return total_weight;
}
int main() {
int n, m;
cin >> n >> m;
vector<Edge> edges;
for (int i = 0; i < m; i++) {
int u, v, weight;
cin >> u >> v >> weight;
edges.emplace_back(u, v, weight);
}
int min_weight = kruskal(edges, n);
cout << "最小生成树的总权值:" << min_weight << endl;
return 0;
}题目描述:假设你正在爬楼梯,需要n阶才能到达楼顶。每次你可以爬1或2个台阶,有多少种不同的方法可以爬到楼顶?
解题思路:这是一个经典的动态规划问题。设dp[i]表示爬到第i阶的不同方法数,则dp[i] = dp[i-1] + dp[i-2]。
C++代码实现:
#include <iostream>
#include <vector>
using namespace std;
int climb_stairs(int n) {
if (n <= 2) return n;
vector<int> dp(n + 1);
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
int main() {
int n;
cin >> n;
cout << climb_stairs(n) << endl;
return 0;
}难度系数5的题目是基础级中的难题,开始考察更复杂的数据结构应用和算法设计。以下是8道典型的难度系数5题目解析。
题目描述:给定一个带权有向图,找出从起点到所有其他顶点的最短路径。
解题思路:使用Dijkstra算法,维护一个优先队列,每次选择当前距离最小的顶点进行松弛操作。
C++代码实现:
#include <iostream>
#include <vector>
#include <queue>
#include <climits>
using namespace std;
typedef pair<int, int> pii; // (距离, 顶点)
vector<int> dijkstra(int start, vector<vector<pii>>& graph) {
int n = graph.size();
vector<int> dist(n, INT_MAX);
dist[start] = 0;
priority_queue<pii, vector<pii>, greater<pii>> pq;
pq.push({0, start});
while (!pq.empty()) {
int u = pq.top().second;
int d = pq.top().first;
pq.pop();
if (d > dist[u]) continue; // 已经找到更短的路径
for (auto& edge : graph[u]) {
int v = edge.first;
int weight = edge.second;
if (dist[v] > dist[u] + weight) {
dist[v] = dist[u] + weight;
pq.push({dist[v], v});
}
}
}
return dist;
}
int main() {
int n, m, start;
cin >> n >> m >> start;
vector<vector<pii>> graph(n);
for (int i = 0; i < m; i++) {
int u, v, weight;
cin >> u >> v >> weight;
graph[u].push_back({v, weight});
}
vector<int> shortest_dist = dijkstra(start, graph);
for (int i = 0; i < n; i++) {
if (shortest_dist[i] == INT_MAX) {
cout << "INF" << " ";
} else {
cout << shortest_dist[i] << " ";
}
}
cout << endl;
return 0;
}题目描述:给定一个数组,找出其中最长的严格递增子序列的长度。
解题思路:使用动态规划的方法,dp[i]表示以第i个元素结尾的最长递增子序列的长度。
C++代码实现:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int length_of_lis(vector<int>& nums) {
if (nums.empty()) return 0;
int n = nums.size();
vector<int> dp(n, 1);
int max_len = 1;
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = max(dp[i], dp[j] + 1);
}
}
max_len = max(max_len, dp[i]);
}
return max_len;
}
int main() {
int n;
cin >> n;
vector<int> nums(n);
for (int i = 0; i < n; i++) {
cin >> nums[i];
}
cout << length_of_lis(nums) << endl;
return 0;
}题目描述:给定一个数组,多次查询区间和,以及区间更新操作。
解题思路:使用前缀和来快速计算区间和,使用差分数组来高效处理区间更新操作。
C++代码实现:
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<long long> nums(n + 1);
vector<long long> prefix_sum(n + 1, 0);
vector<long long> diff(n + 2, 0); // 差分数组,大小为n+2,避免边界检查
for (int i = 1; i <= n; i++) {
cin >> nums[i];
prefix_sum[i] = prefix_sum[i - 1] + nums[i];
}
while (m--) {
int op;
cin >> op;
if (op == 1) { // 区间查询
int l, r;
cin >> l >> r;
cout << prefix_sum[r] - prefix_sum[l - 1] << endl;
} else if (op == 2) { // 区间更新
int l, r, val;
cin >> l >> r >> val;
// 更新差分数组
diff[l] += val;
diff[r + 1] -= val;
// 重新计算前缀和
vector<long long> new_nums(n + 1);
long long current = 0;
for (int i = 1; i <= n; i++) {
current += diff[i];
new_nums[i] = nums[i] + current;
}
// 重新计算前缀和数组
for (int i = 1; i <= n; i++) {
prefix_sum[i] = prefix_sum[i - 1] + new_nums[i];
}
}
}
return 0;
}题目描述:给定一棵二叉树,求其最大深度。
解题思路:可以使用递归或迭代的方法来计算二叉树的深度。
C++代码实现:
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
// 二叉树节点的定义
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
// 递归方法
int max_depth_recursive(TreeNode* root) {
if (!root) return 0;
int left_depth = max_depth_recursive(root->left);
int right_depth = max_depth_recursive(root->right);
return max(left_depth, right_depth) + 1;
}
// 迭代方法(层序遍历)
int max_depth_iterative(TreeNode* root) {
if (!root) return 0;
int depth = 0;
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
int level_size = q.size();
depth++;
for (int i = 0; i < level_size; i++) {
TreeNode* node = q.front();
q.pop();
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
}
return depth;
}
// 构建二叉树
TreeNode* build_tree(vector<int>& nums, int index) {
if (index >= nums.size() || nums[index] == -1) return NULL;
TreeNode* root = new TreeNode(nums[index]);
root->left = build_tree(nums, 2 * index + 1);
root->right = build_tree(nums, 2 * index + 2);
return root;
}
int main() {
int n;
cin >> n;
vector<int> nums(n);
for (int i = 0; i < n; i++) {
cin >> nums[i];
}
TreeNode* root = build_tree(nums, 0);
cout << "递归方法:" << max_depth_recursive(root) << endl;
cout << "迭代方法:" << max_depth_iterative(root) << endl;
return 0;
}题目描述:给定一个由n个节点组成的图,初始时每个节点都是孤立的。然后进行m次操作,每次操作可以是合并两个节点所在的集合,或者查询两个节点是否在同一个集合中。
解题思路:使用并查集来高效地处理集合的合并和查询操作。
C++代码实现:
#include <iostream>
#include <vector>
using namespace std;
class UnionFind {
private:
vector<int> parent;
vector<int> rank; // 用于按秩合并
public:
UnionFind(int n) {
parent.resize(n);
rank.resize(n, 0);
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]); // 路径压缩
}
return parent[x];
}
void unite(int x, int y) {
x = find(x);
y = find(y);
if (x == y) return;
// 按秩合并
if (rank[x] < rank[y]) {
parent[x] = y;
} else {
parent[y] = x;
if (rank[x] == rank[y]) {
rank[x]++;
}
}
}
bool is_connected(int x, int y) {
return find(x) == find(y);
}
};
int main() {
int n, m;
cin >> n >> m;
UnionFind uf(n);
while (m--) {
int op, a, b;
cin >> op >> a >> b;
if (op == 1) { // 合并
uf.unite(a, b);
} else if (op == 2) { // 查询
if (uf.is_connected(a, b)) {
cout << "Yes" << endl;
} else {
cout << "No" << endl;
}
}
}
return 0;
}题目描述:实现KMP算法,在文本中查找模式串的出现位置。
解题思路:KMP算法的核心是构建部分匹配表(next数组),利用已经匹配过的信息来避免不必要的比较。
C++代码实现:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
vector<int> compute_lps(const string& pattern) {
int m = pattern.size();
vector<int> lps(m, 0);
int len = 0; // 前一个状态的LPS值
int i = 1;
while (i < m) {
if (pattern[i] == pattern[len]) {
len++;
lps[i] = len;
i++;
} else {
if (len != 0) {
len = lps[len - 1];
} else {
lps[i] = 0;
i++;
}
}
}
return lps;
}
vector<int> kmp_search(const string& text, const string& pattern) {
vector<int> result;
int n = text.size();
int m = pattern.size();
if (m == 0) return result;
vector<int> lps = compute_lps(pattern);
int i = 0; // 文本的索引
int j = 0; // 模式串的索引
while (i < n) {
if (pattern[j] == text[i]) {
i++;
j++;
}
if (j == m) {
result.push_back(i - j);
j = lps[j - 1];
} else if (i < n && pattern[j] != text[i]) {
if (j != 0) {
j = lps[j - 1];
} else {
i++;
}
}
}
return result;
}
int main() {
string text, pattern;
cin >> text >> pattern;
vector<int> positions = kmp_search(text, pattern);
if (positions.empty()) {
cout << "Pattern not found" << endl;
} else {
cout << "Pattern found at positions: " << endl;
for (int pos : positions) {
cout << pos << " ";
}
cout << endl;
}
return 0;
}题目描述:给定n个物品,每个物品有重量和价值,以及一个容量为C的背包。选择一些物品放入背包,使得总价值最大,且总重量不超过背包容量。
解题思路:使用动态规划的方法,dp[i][j]表示前i个物品中选择一些放入容量为j的背包的最大价值。
C++代码实现:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int knapsack(int capacity, vector<int>& weights, vector<int>& values) {
int n = weights.size();
vector<vector<int>> dp(n + 1, vector<int>(capacity + 1, 0));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= capacity; j++) {
if (weights[i - 1] <= j) {
// 可以选择放入或者不放入当前物品
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] + values[i - 1]);
} else {
// 不能放入当前物品
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[n][capacity];
}
int main() {
int n, capacity;
cin >> n >> capacity;
vector<int> weights(n);
vector<int> values(n);
for (int i = 0; i < n; i++) {
cin >> weights[i] >> values[i];
}
cout << knapsack(capacity, weights, values) << endl;
return 0;
}题目描述:给定一个有向无环图(DAG),输出其拓扑排序序列。
解题思路:使用Kahn算法进行拓扑排序。首先计算每个节点的入度,然后将入度为0的节点加入队列。每次从队列中取出一个节点,将其加入结果序列,并减少其邻接节点的入度。如果邻接节点的入度变为0,则将其加入队列。
C++代码实现:
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
vector<int> topological_sort(int n, vector<vector<int>>& graph) {
vector<int> in_degree(n, 0);
for (int u = 0; u < n; u++) {
for (int v : graph[u]) {
in_degree[v]++;
}
}
queue<int> q;
for (int i = 0; i < n; i++) {
if (in_degree[i] == 0) {
q.push(i);
}
}
vector<int> result;
while (!q.empty()) {
int u = q.front();
q.pop();
result.push_back(u);
for (int v : graph[u]) {
in_degree[v]--;
if (in_degree[v] == 0) {
q.push(v);
}
}
}
// 检查是否存在环
if (result.size() != n) {
return {}; // 存在环,返回空数组
}
return result;
}
int main() {
int n, m;
cin >> n >> m;
vector<vector<int>> graph(n);
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
graph[u].push_back(v);
}
vector<int> topo_order = topological_sort(n, graph);
if (topo_order.empty()) {
cout << "The graph contains a cycle" << endl;
} else {
for (int node : topo_order) {
cout << node << " ";
}
cout << endl;
}
return 0;
}通过对2025年IO竞赛基础级题目的解析,我们可以总结出以下解题技巧:
解题技巧: 理解问题 → 设计算法 → 分析复杂度 → 编写代码 → 测试调试对于已经掌握了基础级知识,想要进一步提高的选手,以下是一些学习建议:
进阶学习资源推荐:
- 书籍:《算法竞赛进阶指南》、《算法导论》
- 网站:Codeforces、AtCoder、OI Wiki
- 比赛:NOIP、CSP等竞赛真题IO竞赛基础级题目的难度适中,主要考察基础数据结构的应用和算法设计能力。通过系统的学习和练习,选手可以逐步掌握解决这些题目的方法和技巧,为进一步学习更高级的知识打下坚实的基础。希望本文的解析能够帮助读者在IO竞赛的道路上取得更好的成绩。
学习价值分布:
数据结构应用(35%) | 算法设计(30%) | 数学基础(20%) | 问题解决能力(15%)互动讨论: