2025年3月GESP真题及题解(C++七级): 图上移动

题目描述
小 A 有一张包含 n n n 个结点与 m m m 条边的无向图,结点以 1 , 2 , ... , n 1, 2, \dots, n 1,2,...,n 标号。小 A 会从图上选择一个结点作为起点,每一步移动到某个与当前小 A 所在结点相邻的结点。对于每个结点 i i i ( 1 ≤ i ≤ n 1 \leq i \leq n 1≤i≤n),小 A 想知道从结点 i i i 出发恰好移动 1 , 2 , ... , k 1, 2, \dots, k 1,2,...,k 步之后,小 A 可能会位于哪些结点。由于满足条件的结点可能有很多,你只需要求出这些结点的数量。
输入格式
第一行,三个正整数 n , m , k n, m, k n,m,k,分别表示无向图的结点数与边数,最多移动的步数。
接下来 m m m 行,每行两个正整数 u i , v i u_i, v_i ui,vi,表示图中的一条连接结点 u i u_i ui 与 v i v_i vi 的无向边。
输出格式
共 n n n 行,第 i i i 行 ( 1 ≤ i ≤ n 1 \leq i \leq n 1≤i≤n) 包含 k k k 个整数,第 j j j 个整数 ( 1 ≤ j ≤ k 1 \leq j \leq k 1≤j≤k) 表示从结点 i i i 出发恰好移动 j j j 步之后可能位置的结点数量。
输入输出样例 1
输入 1
4 4 3
1 2
1 3
2 3
3 4
输出 1
2 4 4
2 4 4
3 3 4
1 3 3
说明/提示
对于 20 % 20\% 20% 的测试点,保证 k = 1 k = 1 k=1。
对于另外 20 % 20\% 20% 的测试点,保证 1 ≤ n ≤ 50 , 1 ≤ m ≤ 50 1 \leq n \leq 50, 1 \leq m \leq 50 1≤n≤50,1≤m≤50。
对于所有测试点,保证 1 ≤ n ≤ 500 , 1 ≤ m ≤ 500 , 1 ≤ k ≤ 20 , 1 ≤ u i , v i ≤ n 1 \leq n \leq 500, 1 \leq m \leq 500, 1 \leq k \leq 20, 1 \leq u_i, v_i \leq n 1≤n≤500,1≤m≤500,1≤k≤20,1≤ui,vi≤n。
思路分析
本题要求计算从每个结点出发,恰好移动 1 到 k 步后可能到达的不同结点数量。由于 k 较小(≤20),可以采用动态规划方法对每个起点单独计算。
对于每个起点 s:
- 定义状态
dp[j][v]表示从 s 出发恰好 j 步能否到达结点 v。 - 初始状态:
dp[0][s] = true,其余为 false。 - 状态转移:对于步数 j 从 1 到 k,遍历每条边 (u, v),若
dp[j-1][u]为 true,则dp[j][v]置为 true;同理,若dp[j-1][v]为 true,则dp[j][u]置为 true。因为是无向图,每条边可以双向行走。 - 最终对于每个 j,统计
dp[j][v]中 true 的数量即为答案。
时间复杂度:共 n 个起点,每个起点进行 k 轮转移,每轮遍历 m 条边,总复杂度 O(n * k * m),在题目数据范围(n,m ≤ 500, k ≤ 20)下完全可行。
代码实现
cpp
#include <iostream>
#include <cstring>
using namespace std;
const int MAXN = 505; // 最大结点数
const int MAXK = 25; // 最大步数+5
const int MAXM = 505; // 最大边数
bool dp[MAXK][MAXN]; // dp[步数][结点]:从当前起点出发恰好j步能否到达该结点
pair<int, int> edges[MAXM]; // 存储所有边
int main() {
int n, m, k;
cin >> n >> m >> k;
// 读入边
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
edges[i] = {u, v}; // 存为pair方便使用
}
// 对每个起点分别计算
for (int start = 1; start <= n; start++) {
// 初始化dp数组为false
memset(dp, 0, sizeof(dp));
// 0步时只能到达起点自身
dp[0][start] = true;
// 动态规划:计算恰好j步的可达性
for (int j = 1; j <= k; j++) {
// 遍历每条边进行转移
for (int i = 0; i < m; i++) {
int u = edges[i].first, v = edges[i].second;
// 如果上一步能到达u,则这一步可以通过边(u,v)到达v
if (dp[j - 1][u]) {
dp[j][v] = true;
}
// 无向图,反向同理
if (dp[j - 1][v]) {
dp[j][u] = true;
}
}
}
// 输出结果:对于每个步数j,统计可达结点数量
for (int j = 1; j <= k; j++) {
int cnt = 0;
for (int v = 1; v <= n; v++) {
if (dp[j][v]) cnt++;
}
cout << cnt;
if (j < k) cout << " "; // 行内空格分隔
}
cout << endl; // 换行进入下一个起点
}
return 0;
}
功能分析
-
输入处理:
- 读取结点数 n、边数 m 和最大步数 k。
- 存储 m 条无向边。
-
核心计算:
- 外层循环遍历每个起点 start(1 到 n)。
- 对每个起点,使用二维布尔数组 dp 记录可达性。
- 动态规划过程模拟了所有可能的移动路径(允许重复访问结点和边)。
- 通过遍历所有边进行状态转移,确保考虑所有可能的移动。
-
输出结果:
- 对每个起点,输出 k 个整数,分别表示移动 1 到 k 步后可能位置的数量。
-
算法特点:
- 利用 k 较小的条件,采用朴素的动态规划即可高效求解。
- 每个起点独立计算,逻辑清晰,易于实现。
- 正确处理无向边和自环(如果存在)的情况。
-
复杂度:
- 时间复杂度:O(n * k * m),最大为 500 × 20 × 500 = 5 × 10^6,运行速度快。
- 空间复杂度:O(k * n + m),主要用于存储 dp 数组和边列表。
各种学习资料,助力大家一站式学习和提升!!!
cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
cout<<"########## 一站式掌握信奥赛知识! ##########";
cout<<"############# 冲刺信奥赛拿奖! #############";
cout<<"###### 课程购买后永久学习,不受限制! ######";
return 0;
}
1、csp信奥赛高频考点知识详解及案例实践:
CSP信奥赛C++动态规划:
https://blog.csdn.net/weixin_66461496/category_13096895.html点击跳转
CSP信奥赛C++标准模板库STL:
https://blog.csdn.net/weixin_66461496/category_13108077.html 点击跳转
信奥赛C++提高组csp-s知识详解及案例实践:
https://blog.csdn.net/weixin_66461496/category_13113932.html
2、csp信奥赛冲刺一等奖有效刷题题解:
CSP信奥赛C++初赛及复赛高频考点真题解析(持续更新):https://blog.csdn.net/weixin_66461496/category_12808781.html 点击跳转
CSP信奥赛C++一等奖通关刷题题单及题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12673810.html 点击跳转
3、GESP C++考级真题题解:

GESP(C++ 一级+二级+三级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12858102.html 点击跳转

GESP(C++ 四级+五级+六级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12869848.html 点击跳转

GESP(C++ 七级+八级)真题题解(持续更新):
https://blog.csdn.net/weixin_66461496/category_13117178.html
4、CSP信奥赛C++竞赛拿奖视频课:
https://edu.csdn.net/course/detail/40437 点击跳转

· 文末祝福 ·
cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
cout<<"跟着王老师一起学习信奥赛C++";
cout<<" 成就更好的自己! ";
cout<<" csp信奥赛一等奖属于你! ";
return 0;
}