寒假ACM集训复习总结Day4-helman
这一天讲的是DFS,BFS深度优先搜索和广度优先搜索
就算是第二次再看题目,也觉得很难...
https://vjudge.net/contest/283487#problem/A
看着一遍源代码也说不出自己到底懂没有,只能说说看完代码后自己懂了多少
这种迷宫题是典型的广度搜索题,不同一般的广度搜索题的是,这道题需要用到两个图
意思就是一般的题是找一个标记为0的点后,不断向外标记,得出终点的标记值
这道题不仅要创一个以起点为起点的图,根据走的步数向外标记的图——简称人物图吧
///说起来,的确没考虑为什么一找到终点,此时的标记值就是最短距离呢,这是因为广度搜索的分支是同时进行的,找到即最短
还要创一个以怪物为起点的,根据怪物行走范围向外标记的图——简称怪物图吧
用怪物图来限制人物图的范围——就是人在人物图上走的时候怪物图上有标记的不能走啦
这应该就是初次做比较难想到的吧
#include <bits/stdc++.h> using namespace std; typedef pair<int ,int >P; const int dx[]={-1,1,0,0}; const int dy[]={0,0,-1,1}; const int INF=0x3f3f3f3f; const int maxn=2e5+7; vector<char >mpa[maxn]; vector<int >mp1[maxn],mp2[maxn]; char str[maxn]; int n,m,d,sx,sy,fx,fy; queue<P>que; int bfs(int a,int b){ for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) mp2[i][j]=INF; mp2[a][b]=0; if(mp1[a][b]>d)que.push(P(a,b)); while(!que.empty()){ P now=que.front(); que.pop(); int x=now.first,y=now.second; if(x==fx&&y==fy)break; for(int i=0;i<4;i++){ int xx=x+dx[i],yy=y+dy[i]; if(xx<1||xx>n||yy<1||yy>m||mp2[xx][yy]!=INF||mp1[xx][yy]<=d)continue; mp2[xx][yy]=mp2[x][y]+1; que.push(P(xx,yy)); } } return mp2[fx][fy]; } int main(){ //freopen("1.in","r",stdin); while(scanf("%d %d %d",&n,&m,&d)!=EOF){ for(int i=1;i<=n;i++){ mp1[i].resize(m+1); mp2[i].resize(m+1); mpa[i].resize(m+1); } for(int i=1;i<=n;i++){ scanf("%s",str+1); mpa[i].push_back(0); for(int j=1;j<=m;j++){ mpa[i][j]=str[j]; if(str[j]=='S')sx=i,sy=j; if(str[j]=='F')fx=i,fy=j; } } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ mp1[i][j]=INF; if(mpa[i][j]=='M'){mp1[i][j]=0;que.push(P(i,j));} } } while(!que.empty()){ P now=que.front(); que.pop(); int x=now.first,y=now.second; for(int i=0;i<4;i++){ int xx=x+dx[i],yy=y+dy[i]; if(xx<1||xx>n||yy<1||yy>m||mp1[xx][yy]!=INF)continue; mp1[xx][yy]=mp1[x][y]+1; que.push(P(xx,yy)); } } int ans=bfs(sx,sy); if(ans==INF)cout<<-1<<endl; else cout<<ans<<endl; } return 0; }
https://vjudge.net/contest/283487#problem/B
这道就很简单了,只用一个图来标识
这是用赋值INF来表示没走过的
#include <iostream> #include<queue> #include<string.h> using namespace std; const int INF=0x3f3f3f3f; const int maxn=1e5+7; int len[maxn]; int k,n; queue<int >que; int bfs(){ int now,next; while(!que.empty()){ now=que.front(); que.pop(); if(now==k)break; if(now-1>=0&&len[now-1]==INF){ next=now-1; len[next]=len[now]+1; que.push(next); } if(now+1<=100000&&len[now+1]==INF){ next=now+1; len[next]=len[now]+1; que.push(next); } if(now*2<=100000&&len[now*2]==INF){ next=now*2; len[next]=len[now]+1; que.push(next); } } return len[now]; } int main(){ while(cin>>n>>k){ while(!que.empty()) que.pop(); memset(len,INF,sizeof(len)); len[n]=0; que.push(n); cout<<bfs()<<endl; } return 0; }
还有个用vis数组来表示走没走过的,不过不是我写的,是做题后学长发的答案代码
#include <cstdio> #include <cstring> #include <queue> #include <iostream> #include <algorithm> using namespace std; int vis[100005]; int n, k; struct Node{ int x,s; }; queue<Node>q; int bfs(){ Node now, next; while (!q.empty()){ now = q.front(); q.pop(); if (now.x == k)break; if (now.x - 1 >= 0 && !vis[now.x - 1]){ next.x = now.x - 1; vis[next.x] = 1; next.s = now.s + 1; q.push(next); } if (now.x + 1 <=100000 && !vis[now.x + 1]){ next.x = now.x + 1; vis[next.x] = 1; next.s = now.s + 1; q.push(next); } if (now.x *2 <= 100000 && !vis[now.x * 2]){ next.x = now.x * 2; vis[next.x] = 1; next.s = now.s + 1; q.push(next); } } return now.s; } int main(){ Node now; while (scanf("%d%d", &n, &k) != EOF){ memset(vis, 0, sizeof(vis)); while (!q.empty())q.pop(); now.x = n; now.s = 0; vis[now.x] = 1; q.push(now); printf("%d\n", bfs()); } return 0; }
https://vjudge.net/contest/283487#problem/C
这道题有点怪,不求到达终点需要的步数,反而要我们把走的路径输出来
用一个结构体,在一个结构里除了装自己的行列还装上一个数的行列行不行
当然不行,这样虽然知道第一个位置就相当第二个位置,但第二个位置无法指向第三个位置
干脆形象一点,在一根绳子上打结,打的结就是走过的点
不过如何通过绳子找到之前的点呢,很简单,只要找上一个结就行了
这样的话绳子会有分叉,因为我们是让新产生的结指向原来的结,不是让原来的结只指向一个新的结
所以当这个结到达终点,就能通过这一条绳得到之前走过的点,当然,其他分支就都无效了
#include<bits/stdc++.h> using namespace std; const int INF=0x3f3f3f3f; int maze[5][5]; bool vis[5][5]; const int dx[]={0,0,1,-1}; const int dy[]={1,-1,0,0}; struct node{ int x;int y;int pre; }; node q[100]; void print(node s){ if(s.pre!=-1)print(q[s.pre]); printf("(%d, %d)\n",s.x,s.y); } void bfs(){ memset(vis,0,sizeof(vis)); vis[0][0]=1; node s,t,next; int front=0,end=0; s.x=0,s.y=0,s.pre=-1; q[end++]=s; while(front<end){ t=q[front++]; int a=t.x,b=t.y; if(a==4&&b==4){print(t);return;} for(int i=0;i<4;i++){ int xx=a+dx[i],yy=b+dy[i]; if(xx<0||yy<0||xx>4||yy>4||vis[xx][yy]||maze[xx][yy]==1)continue; next.x=xx,next.y=yy,next.pre=front-1; q[end++]=next; } } } int main(){ for(int i=0;i<5;i++) for(int j=0;j<5;j++){ cin>>maze[i][j]; } bfs(); return 0; }
https://vjudge.net/contest/283487#problem/D
搜索的本质是什么——就是往可能的方向发展,一旦搜索到了,就停止搜索
而广度搜索擅长处理走地图问题,从一点出发向各个方向延伸,直到达到目的地
重点是要有一个不同方向延伸的过程,这样的题才能用广度搜索
而深度搜索则是处理广度处理不了的问题
深度多用递归,就是对下一步按上一步的处理方法进行处理,看是否可行,与广度搜索本质相同
只是当使用方向延伸不能满足题目要求时,需要对问题具体情况具体分析时,就要用到深度搜索写一个单独的处理方法
#include<bits/stdc++.h> using namespace std; int x[11],ans[11]; int n,sum; bool judge(int k){ for(int i=1;i<k;i++) if(x[i]==x[k]||abs(i-k)==abs(x[i]-x[k]))return false; return true; } void dfs(int k){ if(k>n){ sum++; return; } for(int i=1;i<=n;i++){ x[k]=i; if(judge(k))dfs(k+1); } } int main(){ for(int i=1;i<=10;i++){ sum=0; n=i; dfs(1); ans[i]=sum; } while(cin>>n&&n){ cout<<ans[n]<<endl; } return 0; }
再者如果在优先搜索中遇到判断行走限制(不是指走出地图)或是有障碍的情况,就要编写judge函数
例如下面这道
https://vjudge.net/contest/283487#problem/F
#include<bits/stdc++.h> using namespace std; int p[5][5]; int high,num; int n; bool judge(int x,int y){ for(int i=x;i>=1;i--){ if(p[i][y]==1)return 0; if(p[i][y]==2)break; } for(int i=x;i<=n;i++){ if(p[i][y]==1)return 0; if(p[i][y]==2)break; } for(int i=y;i>=1;i--){ if(p[x][i]==1)return 0; if(p[x][i]==2)break; } for(int i=y;i<=n;i++){ if(p[x][i]==1)return 0; if(p[x][i]==2)break; } } void dfs(){ if(num>high) high=num; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){ if(p[i][j]==0&&judge(i,j)){ num++; p[i][j]=1; dfs(); p[i][j]=0; num--; } } } int main(){ //int n; char ch; while(cin>>n&&n){ memset(p,0,sizeof(p)); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){ cin>>ch; if(ch=='X')p[i][j]=2; } high=num=0; dfs(); cout<<high<<endl; } return 0; }