传送门
题目描述
小菡很聪明,所以他打ACM非常给力,经常偷偷学习到深夜
他是如此的努力学习,以至于他根本就没有时间完整的逛过学校。
有一天,他听说科大湖的黑天鹅非常好看,由于没有女朋友,他便独自一个人去了。
然而他还在专心致志的观赏黑天鹅,丝毫没有意识到集训还有 k 分钟就要开始了,不幸的是刚好小菡是一个路痴。
你觉得他在 k 分钟内可以赶到创客参加集训吗?
如果可以,他最少要花多少时间才可以回到创客空间参加集训呢?这样子的路径有多少条?
题意
求最短路径方案数
1.可以用dfs()记忆化搜索 + 3个剪枝 (具体见代码)
对一个点进行深搜前要将其标记为走过,防止两个点来回走死循环,对一个点深搜后,要把这个点再标记为没走过,因为别的搜索方案可能到这个点花的时间可以更少。
AC代码
#include <bits/stdc++.h>
using namespace std;
char path[1010][1010];
int visited[1010][1010];
int MOVE[4][2] = {{-1,0},{1,0},{0,-1},{0,1}};
int T,n,m,k;
int ans;
int x,y; //记录创客坐标
int flag1,flag; //flag1记录是否已经早到最短路径
bool right_index(int i,int j){
return 0 <= i && i < n && 0 <= j && j < m;
}
void dfs(int i,int j,int time){// 坐标 和已经使用的时间
int i1,j1;
if(time > k)//路上花的时间已经大于最短时间
return ;
if(visited[i][j] && visited[i][j] < time)//这个点已经走过并且之前用了更短的时间,所以现在的路径一定不是最短路
return ;
visited[i][j] = time;
if(flag1 && k-time < abs(x-i) + abs(y-j))//到终点可能的最短时间大于已有的最短时间
return ;
if(path[i][j] == 'C'){
if(time == k){
ans++;
}
else{ //time < k
ans = 1;
k = time;//更新最短距离
}
x = i;
y = j;
flag1 = 1; //标记已经有了最短路径
return ;
}
path[i][j] = '#';//把走过的路标记
for(int g = 0;g < 4;g++){
i1 = i + MOVE[g][0];
j1 = j + MOVE[g][1];
if(right_index(i1,j1) && path[i1][j1] != '#'){
dfs(i1,j1,time + 1);
}
}
path[i][j] = '*';//退回上一步,所以标记为没有走过
return ;
}
int main(){
cin >> T;
for(int t = 0;t < T;t++){
memset(visited,0,sizeof(visited));
ans = 0;
cin >> n >> m >> k;
for(int i = 0;i < n;i++){
scanf("%s",path[i]);
}
flag1 = 0;
flag = 0;
for(int i = 0;i < n;i++){
for(int j = 0;j < m;j++)
if(path[i][j] == 'L'){
dfs(i,j,0);
if(ans)
cout << "Case #"<< t+1 <<": " << k << " " << ans << " " << endl;
else cout << "Case #"<< t+1 <<": " << -1 << endl;
flag = 1;
break;
}
if(flag)
break;
}
}
//system("pause");
return 0;
}
2.bfs求解,比较难理解
求最短路可以用bfs(),但要求方案数(存在不同的路径到达同一点,且所花时间相同且最短),考虑花费为相同的一层遍历要完全走完,并且记录走了几圈,也就是花了多少时间。
对于走过的点不能立即标记为访问过,而是出队后再标记为访问过,因为bfs()是一圈一圈的往外走,每一圈可以对应成一个时间,这一圈所花的时间是上一圈的时间+1。如果上一圈的下一步没有全部走完,这个点可能会被上一圈再次走到,所以不能立即 标记为不能再走,防止一个点退回上一圈,所以当点所在圈已经走完,它的上一圈没必要再走一次,也就是每个点出队后就可以把它标记为访问过(不让它再被入队)。
AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
char graph[1020][1020];//图
int visited[1020][1020];//记录所需时间
int MOVE[4][2] ={{-1,0},{1,0},{0,1},{0,-1}};
ll n,m,k;
ll ans;
struct point{//点的坐标
int x,y;
};
bool right_index(int i,int j){//判断下标合法性
return 0 <= i && i < n && 0 <= j && j < m;
}
int bfs(int i,int j){ //从起点开始一圈一圈的往外扩张,外圈所花时间比里圈多1
queue<point>q;
point u,v;
u.x = i;
u.y = j;
q.push(u);
int cnt = 0;
int x1,y1;
while(!q.empty() && cnt < k){
u = q.front();
q.pop();
//cout << u.x << ' ' << u.y << endl;
for(int i = 0;i < 4;i++){
v.x = u.x + MOVE[i][0];
v.y = u.y + MOVE[i][1];
if(right_index(v.x,v.y)){
if(graph[v.x][v.y] == '*'){
q.push(v);
visited[v.x][v.y] = visited[u.x][u.y] + 1;
}else if(graph[v.x][v.y] == 'C'){
visited[v.x][v.y] = visited[u.x][u.y] + 1;
ans++;
x1 = v.x;//记录创客坐标
y1 = v.y;
}
}
}
graph[u.x][u.y] = '#'; //剪枝 已经开始走u的下一圈,防止点u再走
if(!q.empty()){
v = q.front();
if(visited[u.x][u.y] != visited[v.x][v.y]){//如果点u和下一个点
cnt++; //到达的所需时间不同就说明
} //说明上个点是它那个圈的最后一个点
}
}
if(ans)//如果找到路径 返回所需最短时间
return visited[x1][y1];
else return 0;
}
int main(){
int T;
int flag = 0;
cin >> T;
for(int t = 0;t < T;t++){
ans = flag = 0;
memset(graph,0,sizeof(graph));
memset(visited,0,sizeof(visited));
scanf("%d %d %d",&n,&m,&k);
for(int i = 0;i < n;i++){
scanf("%s",graph[i]);
//cout << graph[i] << endl;
}
for(int i = 0;i < n;i++){
for(int j = 0;j < m;j++){
if(graph[i][j] =='L'){//找起点
k = bfs(i,j);
flag = 1;
break;
}
}
if(flag)
break;
}
if(ans)
printf("Case #%d: %d %d\n",t+1,k,ans);
else printf("Case #%d: -1\n",t+1);
}
//system("pause");
return 0;
}