P11228 [CSP-J 2024] 地图探险

记录51

#include<bits/stdc++.h>
using namespace std;
char mp[1010][1010]={};
int vis[1010][1010]={};
int dx[5]={0,1,0,-1};
int dy[5]={1,0,-1,0};
int main(){
	int T,n,m,k,x,y,d;
	cin>>T;
	while(T--){
		cin>>n>>m>>k;
		cin>>x>>y>>d;
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++) cin>>mp[i][j];
		}
		int ans=1;
    vis[x][y]=1;
    	//int cnt=1;
		while(k--){
      		//printf("第%d次操作:",cnt);
			int xn=x+dx[d];
			int yn=y+dy[d];
			if(xn>=1&&xn<=n&&yn>=1&yn<=m&&mp[xn][yn]=='.'){
				if(vis[xn][yn]==0){
					ans++;
					vis[xn][yn]=1;
				}
				x=xn;
				y=yn;
        		//printf("走到(%d,%d)  第%d个位置\n",xn,yn,ans);
			}
			else{
        		d=++d%4;
        		//printf("向%d方向转向\n",d);
      		} 
      		//cnt++;
		}
		cout<<ans<<endl;
    	memset(vis,0,sizeof(vis));
	}
	return 0;
} 

题目传送门https://www.luogu.com.cn/problem/P11228


突破口

初始时,机器人的位置为 (x0​,y0​),朝向为 d0​。保证初始时机器人所在的位置为空地。接下来机器人将要进行 k 次操作。每一步,机器人将按照如下的模式操作

👉转向也算一次操作

保证字符串中只包含 x 和 . 两个字符。其中,第 x 行的字符串的第 y 个字符代表的位置为 (x,y)。这个位置是 x 即代表它是障碍,否则代表它是空地。数据保证机器人初始时所在的位置为空地。

👉有障碍物

地图上所有被机器人经过的位置(包括起始位置)有几个。

👉计算走的格子数,不重复计数


思路

并不是bfs跟dfs的题目,但是如果会搜索就很轻松做本题,这题纯模拟

  1. 构建地图
  2. 寻找可行路径
  3. 计数走过的格子

代码简析

#include<bits/stdc++.h>
using namespace std;
char mp[1010][1010]={};
int vis[1010][1010]={};
int dx[5]={0,1,0,-1};
int dy[5]={1,0,-1,0};
int main(){
	int T,n,m,k,x,y,d;
	cin>>T;
	while(T--){
		cin>>n>>m>>k;
		cin>>x>>y>>d;
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++) cin>>mp[i][j];
		}
    }
	...
	return 0;
} 

char mp[1010][1010]={};👉地图空间

int vis[1010][1010]={};👉判断地图上的点是否访问过,防止重复计数

int dx[5]={0,1,0,-1};👉x方向移动,上(减)下(加)移动

int dy[5]={1,0,-1,0};👉y方向移动,左(减)右(加)移动

注意:二者结合起来看,就是右下左上的逆时针转动

int T(几组数据),n(行数),m(列数),k(次数),x(停留的点),y(停留的点),d(转动方向);

#include<bits/stdc++.h>
using namespace std;
char mp[1010][1010]={};
int vis[1010][1010]={};
int dx[5]={0,1,0,-1};
int dy[5]={1,0,-1,0};
int main(){
	int T,n,m,k,x,y,d;
	cin>>T;
	while(T--){
		cin>>n>>m>>k;
		cin>>x>>y>>d;
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++) cin>>mp[i][j];
		}
		int ans=1;
    vis[x][y]=1;
    	//int cnt=1;
		while(k--){
      		//printf("第%d次操作:",cnt);
			int xn=x+dx[d];
			int yn=y+dy[d];
			if(xn>=1&&xn<=n&&yn>=1&yn<=m&&mp[xn][yn]=='.'){
				if(vis[xn][yn]==0){
					ans++;
					vis[xn][yn]=1;
				}
				x=xn;
				y=yn;
        		//printf("走到(%d,%d)  第%d个位置\n",xn,yn,ans);
			}
			else{
        		d=++d%4;
        		//printf("向%d方向转向\n",d);
      		} 
      		//cnt++;
		}
		cout<<ans<<endl;
    	memset(vis,0,sizeof(vis));
	}
	return 0;
} 

int ans=1;👉起点是经过的第一个格子

vis[x][y]=1;👉地图上记录起点访问过

while(k--){}👉只执行k次操作

int xn=x+dx[d];   int yn=y+dy[d];👉记录接下来移动后的位置

if(xn>=1&&xn<=n&&yn>=1&yn<=m&&mp[xn][yn]=='.'){}👉判断路径是否可行

ans++;     vis[xn][yn]=1;👉格子数加1,同时记录这个点经过了

x=xn;  y=yn;👉更新停留的点

else{d=++d%4;} 👉路径不可行的时候进行转向

cout<<ans<<endl;👉输出经过的点

memset(vis,0,sizeof(vis));👉访问数组重置,防止下一组数据被污染


补充

CSP-J DFS/BFS 技巧完全汇总


一、搜索题识别特征(先判断用哪个)

特征DFS(深度优先)BFS(广度优先)
目标路径方案数连通块最短路径/步数最小操作
数据结构图、树、网格图、网格
关键词"有多少种方案"、"路径"、"连通""最少步数"、"最短"、"最快"
典型题迷宫路径数、连通块、选物品迷宫最短路径、倒水问题、编辑距离
CSP-J频率★★★★★★★★★★

二、DFS核心技巧(CSP-J必背)

技巧1:记忆化搜索(DP化)

场景:重复子问题,如迷宫路径数

int dfs(int x, int y) {
    if (x == n && y == m) return 1;
    if (vis[x][y]) return dp[x][y];  // 记忆化
    vis[x][y] = true;
    int ans = 0;
    for (int dir = 0; dir < 4; dir++) {
        int nx = x + dx[dir], ny = y + dy[dir];
        if (valid(nx, ny)) {
            ans += dfs(nx, ny);
        }
    }
    return dp[x][y] = ans;  // 保存结果
}

作用:将O(4ⁿ)降到O(n²)


技巧2:连通块标记(最常用)

场景:统计岛屿数量、洪水填充

void dfs(int x, int y) {
    if (x<1 || x>n || y<1 || y>m || a[x][y]!=1) return;  // 边界
    a[x][y] = 2;  // 标记为已访问
    for (int dir=0; dir<4; dir++) {
        dfs(x+dx[dir], y+dy[dir]);
    }
}

// 统计连通块
int cnt = 0;
for (int i=1; i<=n; i++)
    for (int j=1; j<=m; j++)
        if (a[i][j] == 1) {
            dfs(i, j);
            cnt++;
        }

技巧3:选物品/组合(回溯+剪枝)

场景:从n个物品选k个,和为目标值

bool dfs(int idx, int sum, int chosen) {
    if (chosen == k && sum == target) return true;
    if (idx > n || chosen > k || sum > target) return false;  // 剪枝
    
    // 选当前
    if (dfs(idx+1, sum+a[idx], chosen+1)) return true;
    
    // 不选当前
    if (dfs(idx+1, sum, chosen)) return true;
    
    return false;
}

技巧4:树形DFS(树遍历)

场景:树的深度、子树大小、路径和

int depth[N];
void dfs(int u, int parent) {
    for (int v : g[u]) {
        if (v == parent) continue;  // 避免回父节点
        depth[v] = depth[u] + 1;
        dfs(v, u);
    }
}

三、BFS核心技巧(CSP-J必背)

技巧1:最短路径(最核心)

场景:迷宫最短步数、最少操作

int bfs(int sx, int sy) {
    queue<pair<int, int>> q;
    q.push({sx, sy});
    vis[sx][sy] = 0;  // 距离数组
    
    while (!q.empty()) {
        auto [x, y] = q.front(); q.pop();
        if (x == tx && y == ty) return vis[x][y];
        
        for (int dir=0; dir<4; dir++) {
            int nx = x + dx[dir], ny = y + dy[dir];
            if (valid(nx, ny) && !vis[nx][ny]) {
                vis[nx][ny] = vis[x][y] + 1;
                q.push({nx, ny});
            }
        }
    }
    return -1;  // 无法到达
}

记忆:BFS第一次到达终点时,步数一定是最短的


技巧2:多源BFS(洪水填充优化)

场景:多个起点同时扩展

// 将所有起点同时入队
queue<pair<int, int>> q;
for (int i=1; i<=n; i++)
    for (int j=1; j<=m; j++)
        if (a[i][j] == 起点) {
            q.push({i, j});
            vis[i][j] = 0;
        }

// BFS扩展,谁先到终点谁最短
while (!q.empty()) { ... }

技巧3:0-1 BFS(边权为0或1)

场景:某些方向代价为0,某些为1(如转向代价)

deque<pair<int, int>> q;  // 用双端队列
vis[sx][sy] = 0;
q.push_front({sx, sy});  // 起点从队首入

while (!q.empty()) {
    auto [x, y] = q.front(); q.pop_front();
    for (int dir=0; dir<4; dir++) {
        int nx = x + dx[dir], ny = y + dy[dir];
        int w = (dir == 方向) ? 0 : 1;  // 代价
        if (valid(nx, ny) && vis[x][y] + w < vis[nx][ny]) {
            vis[nx][ny] = vis[x][y] + w;
            if (w == 0) q.push_front({nx, ny});
            else q.push_back({nx, ny});
        }
    }
}

四、DFS vs BFS 选择决策树

是否需要求最短/最少?
├── 是 → BFS(第一次到终点即最短)
│       └── 边权为0/1 → 0-1 BFS/双端队列
│
└── 否 → DFS(找路径、方案数、连通块)
        ├── 有重复子问题 → 记忆化搜索
        ├── 找所有方案 → 回溯+剪枝
        └── 只需遍历 → 普通DFS

五、CSP-J搜索题通用模板

DFS模板(连通块)

const int N = 1010;
int n, m, a[N][N];
bool vis[N][N];
int dx[] = {-1, 1, 0, 0};
int dy[] = {0, 0, -1, 1};

void dfs(int x, int y) {
    if (x<1 || x>n || y<1 || y>m || a[x][y]==0 || vis[x][y]) return;
    vis[x][y] = true;
    for (int dir=0; dir<4; dir++) {
        dfs(x+dx[dir], y+dy[dir]);
    }
}

BFS模板(最短路径)

int bfs(int sx, int sy, int tx, int ty) {
    memset(vis, -1, sizeof(vis));
    queue<pair<int, int>> q;
    q.push({sx, sy});
    vis[sx][sy] = 0;
    
    while (!q.empty()) {
        auto [x, y] = q.front(); q.pop();
        if (x == tx && y == ty) return vis[x][y];
        
        for (int dir=0; dir<4; dir++) {
            int nx = x + dx[dir], ny = y + dy[dir];
            if (nx>=1 && nx<=n && ny>=1 && ny<=m && a[nx][ny]==1 && vis[nx][ny]==-1) {
                vis[nx][ny] = vis[x][y] + 1;
                q.push({nx, ny});
            }
        }
    }
    return -1;  // 无法到达
}

六、性能优化技巧

1. 剪枝(Pruning)

if (当前步数 >= 最优解) return;  // 步数剪枝
if (当前和 > 目标值) return;      // 和剪枝
if (不可能到达) return;            // 可行性剪枝

2. 迭代加深(DFS深度限制)

for (int depth = 1; depth <= MAX_DEPTH; depth++) {
    if (dfs(起点, depth)) {  // 限制深度搜索
        cout << depth;  // 找到最优解
        break;
    }
}

3. 双向BFS

// 从起点和终点同时开始BFS,在中间相遇
queue<pair<int, int>> q1, q2;
// 交替扩展,直到相遇

七、调试与容错技巧

1. 访问越界检查

#define valid(x, y) (x>=1 && x<=n && y>=1 && y<=m && !vis[x][y])
// 或函数形式
inline bool check(int x, int y) {
    return x>=1 && x<=n && y>=1 && y<=m && a[x][y]==1;
}

2. 递归深度限制

// DFS递归深度超过10^5会栈溢出,改用栈模拟迭代
if (depth > 100000) return false;  // 提前退出

3. BFS队列大小

// 最坏情况队列大小 = n×m,开数组要开够
queue<pair<int, int>> q;  // 或用数组模拟队列

八、CSP-J高频搜索题清单

年份题目搜索类型难度
2023 T1小苹果流程模拟(类BFS)★☆☆☆☆
2022 T1乘方DFS(递归)★☆☆☆☆
2021迷宫BFS最短路径★★☆☆☆
2020连通块DFS染色★★☆☆☆
2019选物品DFS回溯★★☆☆☆

九、一句话总结

CSP-J搜索题 = 先判BFS/DFS → 写模板 → 加剪枝 → 防越界 → 调试输出,BFS找最短,DFS找路径,记忆化防超时,全局开大数组。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值