USACO 3.3 Camelot 【广度优先搜索+贪心】
题目描述
很久以前,亚瑟王和他的骑士习惯每年元旦去庆祝他们的友谊。为了纪念上述事件, 我们把这些故事看作是一个棋盘游戏。有一个国王和若干个骑士被放置在一个由许多方格 组成的棋盘上,没有两个骑士在同一个方格内。
这个例子是标准的 8*8 棋盘:
国王可以移动到任何一个相邻的方格,从下图中黑子位置到下图中白子位置前提是他 不掉出棋盘之外。
一个骑士可以从下图中黑子位置移动到下图中白子位置(走“日”字形) 但前提是他 不掉出棋盘之外。
在游戏中,玩家可在每个方格上放不止一个棋子,假定方格足够大,任何棋子都不会 阻碍到其他棋子正常行动。
玩家的任务就是把所有的棋子移动到同一个方格里——用最小的步数。为了完成这个 任务,他必须按照上面所说的规则去移动棋子。另外,玩家可以选择一个骑士跟国王从他 们两个相遇的那个点开始一起行动,这时他们按照骑士的行动规则行动,其他的单独骑士 则自己一直走到集中点。骑士和国王一起走的时候,只算一个人走的步数。
请计算他们集中在一起的最小步数,而且玩家必须自己找出这个集中点。当然,这些 棋子可以在棋盘的任何地方集合。
输入输出格式
输入格式:
第一行: 两个用空格隔开的整数:R,C 分别为棋盘行和列的长。不超过 26 列,40 行。
第二行到结尾: 输入文件包含了一些有空格隔开的字母/数字对,一行有一个或以 上。第一对为国王的位置,接下来是骑士的位置。可能没有骑士,也可能整个棋盘都是骑 士。行从 1 开始,列从大写字母 A 开始。
输出格式:
单独一行表示棋子集中在一个方格的最小步数。
输入
8 8
D 4
A 3 A 8
H 1 H 8
输出
10
分析
此题刚开始给人一种毫无头绪的感觉,因为我们既要顾及让所有人到终点,又要考虑骑士背不背国王的问题,为了从简单入手,我们先不考虑国王,先依次枚举终点(也就是汇合点),用广搜预处理出骑士在棋盘上每个能跳到的点还需几步才能到达终点(dis[x][y])。这里注意,并不是仅仅处理出来最短路径,因为还可能为了去接国王放弃最优。并且标记棋盘上所有的骑士能到的点。
现在满棋盘骑士能到的地方都到了,我们考虑国王。
骑士只能跳6个格子的对角线,那么不论骑士国王分别在哪,我们都能把它转化成在同一个六格中的情形,那么骑士与国王的位置关系就有只能有如下的6种:(蓝点为国王,红点为骑士,只看A红点)
建议读者拿笔自己模拟一下,发现K1,K2,K3,K4,K5骑士都是无法到达的,国王只能自己走过去,而K6本身就在骑士遍历过的点上,不用走。于是我们发现对于每一个终点,只要让国王往四周探索,离骑士的足迹最短的距离dist[x][y](因为追随骑士的足迹显然要比国王自己爱慢慢走要快的多)再加上该点的骑士还需几步到达终点dis[x][y]就是该方案下的最优解,再枚举每一个终点,取最优。
-
预处理骑士的dis[x][y]的时候,一定要从终点开始向外扩展(bfs)
-
计算国王的路径时,一定要从国王的位置开始(bfs)
以上两点确保了搜索的准确性。
下面是参考代码:
c++ #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> using namespace std; struct point { int x,y; }tmp; int n,m,kn=0,ans=1<<29; int kx,ky,knight[1020][3],dis[50][50],vis[50][50]; int dist[50][50],cnt=0; char c[5]; int der_qi[3][9]={ {0,0,0,0,0,0,0,0,0},//我也实在是很无奈 {0,-2,-2,-1,-1,1,1,2,2}, {0,-1,1,-2,2,-2,2,1,-1} }; int der_guo[3][9]={ {0,0,0,0,0,0,0,0,0}, {0,-1,-1,-1,0,0,1,1,1}, {0,-1,1,0,1,-1,-1,1,0} }; bool Judge(int x,int y) { if(x>n || x<1 || y>m || y<1) return false; if(dis[x][y]!=-1) return false;//判断此点骑士有没有跳过 return true; } bool judge(int x,int y) { if(x>n || x<1 || y>m || y<1) return false; if(vis[x][y]==1) return false;//判断此路是否已经被标记 return true; } bool Pan(int x,int y) { if(x>n || x<1 || y>m || y<1) return false; if(dist[x][y]!=-1) return false;//判断此点国王有没有跳过 return true; } int king() { int rec=1<<29; queue<point> q; q.push((point){kx,ky}); memset(dist,-1,sizeof(dist)); dist[kx][ky]=0; while(!q.empty()) { point temp; temp=q.front(); q.pop(); int xx=temp.x,yy=temp.y; if(vis[xx][yy]) rec=min(dist[xx][yy],rec); for(int i=1;i<=8;i++) { int a=xx,b=yy; a=xx+der_guo[1][i]; b=yy+der_guo[2][i]; if(!Pan(a,b)) continue; dist[a][b]=dist[xx][yy]+1; if(vis[a][b]) rec=min(dist[a][b],rec); else q.push((point){a,b}); } } return rec; } void Mark(int x,int y) { int xx=x,yy=y; for(int i=1;i<=8;i++) { xx=x+der_qi[1][i]; yy=y+der_qi[2][i]; if(dis[xx][yy]==dis[x][y]-1 && judge(xx,yy)) { vis[xx][yy]=1; Mark(xx,yy); } } } int bfs(int x,int y)//传进去终点 { memset(vis,0,sizeof(vis)); queue<point> q; vis[x][y]=1; dis[x][y]=0; q.push((point){x,y}); while(!q.empty()) { point temp; temp=q.front(); int xx=temp.x; int yy=temp.y; q.pop(); for(int i=1;i<=8;i++) { int u,v; u=xx+der_qi[1][i]; v=yy+der_qi[2][i]; if(!Judge(u,v)) continue; dis[u][v]=dis[xx][yy]+1; q.push((point){u,v}); } } int rec=0; for(int i=1;i<=kn;i++) { if(dis[knight[i][1]][knight[i][2]]<0) return 1e8; rec+=dis[knight[i][1]][knight[i][2]]; if(rec>=ans) return rec; vis[knight[i][1]][knight[i][2]]=1; Mark(knight[i][1],knight[i][2]); } int a1=king(); rec+=a1; return rec; } int main() { scanf("%d%d",&n,&m); scanf("%s%d",c,&kx); ky=c[0]-'A'+1; while(scanf("%s%d",c,&knight[++kn][1])!=EOF) { knight[kn][2]=c[0]-'A'+1; } kn--; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { memset(dis,-1,sizeof(dis)); int tt=bfs(i,j); ans=min(ans,tt); } cout<<ans<<endl; return 0; }