拖拉机
拖拉机
干了一整天的活,农夫约翰完全忘记了他把拖拉机落在田地中央了。
他的奶牛非常调皮,决定对约翰来场恶作剧。
她们在田地的不同地方放了捆干草,这样一来,约翰想要开走拖拉机就必须先移除一些干草捆。
拖拉机的位置以及捆干草的位置都是二维平面上的整数坐标点。
拖拉机的初始位置上没有干草捆。
当约翰驾驶拖拉机时,他只能沿平行于坐标轴的方向(北,南,东和西)移动拖拉机,并且拖拉机必须每次移动整数距离。
例如,驾驶拖拉机先向北移动单位长度,然后向东移动单位长度。
拖拉机无法移动到干草捆占据的位置。
请帮助约翰确定他需要移除的干草捆的最小数量,以便他能够将拖拉机开到二维平面的原点。
输入格式
第一行包含三个整数:以及拖拉机的初始位置。
接下来行,每行包含一个干草捆的位置坐标。
输出格式
输出约翰需要移除的干草捆的最小数量。
数据范围
,
输入样例:
1 7 6 3 2 6 2 3 5 2 4 4 3 5 2 1 6 7 3 7 5 4 8 6 4
输出样例:
1
解题思路
首先先审题,题目给出的干草捆的坐标范围虽然是,但并不代表整个图的被限制在的矩阵中,整个图其实是无限大的,点还可以走到以外的地方。类似于这样的效果。
但事实上我们不可能去搜索一个无限大的图。因为障碍物的坐标范围为,因此我们可以给的矩阵扩大一圈(扩大的一圈没有障碍物),,也就是只用搜的矩阵就可以了。
接着我们抽象出一个图的模型。首先,每一个格子就是一个点。边的话,就是每个格子上下左右四个方向,相邻的格子可以看作是有边相连的。然后不同的是这题要的并不是边权,而是点权(其实两者差不了多少)。如果一个格子是障碍物的话,那么这个格子的点权是,否则是。
然后我们可以把原问题转化为一个求最短路的问题。
首先要证明这两个问题是等价的。对于任意一个从起点到终点的走法,原问题中移走障碍物的一种走法就对于一条路径,并且移走障碍物的数量就是这条路径的权值和。所以原问题的每一个方案就对应一条从起点到终点的路径,反过来每一条路径对应原问题的一个方案,而且路径长度就是移走障碍物的数量。所以要求原问题的最优解,等价于求最短路。
当时写这题的话完全没有这种抽象的意识,完全没有去这么想,所以写不出来。
然后我们发现,每条边的权重只有和。如果全部边的权重为,我们可以用bfs求最短路。而只有和的话就用双端队列广搜。
其实双端队列广搜本质就是算法,不过由于边权只有和的特殊性,把堆变成了双端队列,时间复杂度为。
原理是,从一个点扩展的时候另一个点的时候,如果边权为,则把插队头;如果边权为,则把插队尾。
下面证明为什么可以用双端队列代替堆。
首先是证明双端队列中所有的点到起点的距离大小最多只有两种,而且如果有两种的话他们的大小只相差。比如,如果有两种不同的距离,那么双端队列前部分的点到起点的距离都是,后部分的点到起点的距离都是。如果只有一种距离,那么双端队列中的点到起点的距离都是。
用归纳法。初始时,双端队列只有一个起点,显然成立。假设当双端队列的大小为时成立,证明当双端队列的大小为时也成立。对于任意一个大小为的双端队列,因为我们每次只能往队列插入一个元素,所以它是由大小为的双端队列扩展出来的。因为对于长度为的双端队列条件是成立的,所以到起点的距离大小不同的点最多只有两段。然后我们每次扩展都是从队头取出元素的,若从队头取出的元素到起点的距离为,扩展的结果只有两种:一种是边权为0,扩展的结果就为,插入队头;另一种是边权为,扩展的结果就为,插入队尾。插完后我们会发现,双端队列中还是最多只有两种不同的距离。前面是队列中有两种不同距离的证明,只有一种距离的证明同理。
所以可以证明对于任何大小的双端队列,队列中所有的点到起点的距离大小最多只有两种,而且前一段的点到起点的距离为,后一段的点到起点的距离为。
也就意味着,任何时刻队列中所有的点,到起点的距离是升序的,所以队头的点到起点的距离一定是最小值,所以可以起到堆的效果。
代码的写法和的很像,只不过这里把优先队列改成了双端队列,边是根据曼哈顿距离来选择的。
AC代码如下:
1 #include <cstdio> 2 #include <cstring> 3 #include <deque> 4 #include <algorithm> 5 using namespace std; 6 7 typedef pair<int, int> PII; 8 9 const int N = 1010; 10 11 int graph[N][N], dist[N][N]; 12 bool vis[N][N]; 13 14 int bfs(int sx, int sy) { 15 deque<PII> dq; 16 dq.push_back({sx, sy}); 17 memset(dist, 0x3f, sizeof(dist)); 18 dist[sx][sy] = 0; 19 20 int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; 21 22 while (!dq.empty()) { 23 PII t = dq.front(); 24 dq.pop_front(); 25 26 if (vis[t.first][t.second]) continue; 27 vis[t.first][t.second] = true; 28 if (vis[0][0]) break; 29 30 for (int i = 0; i < 4; i++) { 31 int x = t.first + dx[i], y = t.second + dy[i]; 32 33 // 点要满足在合法的位置,而且距离变小(入队不需要判重) 34 if (x >= 0 && x < N && y >= 0 && y < N && dist[x][y] > dist[t.first][t.second] + graph[x][y]) { 35 dist[x][y] = dist[t.first][t.second] + graph[x][y]; 36 if (graph[x][y]) dq.push_back({x, y}); // 边权为0加入队头 37 else dq.push_front({x, y}); // 边权为1加入队尾 38 } 39 } 40 } 41 42 return dist[0][0]; 43 } 44 45 int main() { 46 int n, sx, sy; 47 scanf("%d %d %d", &n, &sx, &sy); 48 while (n--) { 49 int x, y; 50 scanf("%d %d", &x, &y); 51 graph[x][y] = 1; 52 } 53 54 printf("%d", bfs(sx, sy)); 55 56 return 0; 57 }
参考资料
AcWing 2019. 拖拉机(寒假每日一题2022):https://www.acwing.com/video/3656/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/15875137.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效