拖拉机

拖拉机

干了一整天的活,农夫约翰完全忘记了他把拖拉机落在田地中央了。

他的奶牛非常调皮,决定对约翰来场恶作剧。

她们在田地的不同地方放了N捆干草,这样一来,约翰想要开走拖拉机就必须先移除一些干草捆。

拖拉机的位置以及N捆干草的位置都是二维平面上的整数坐标点。

拖拉机的初始位置上没有干草捆。

当约翰驾驶拖拉机时,他只能沿平行于坐标轴的方向(北,南,东和西)移动拖拉机,并且拖拉机必须每次移动整数距离。

例如,驾驶拖拉机先向北移动2单位长度,然后向东移动3单位长度。

拖拉机无法移动到干草捆占据的位置。

请帮助约翰确定他需要移除的干草捆的最小数量,以便他能够将拖拉机开到二维平面的原点。

输入格式

第一行包含三个整数:N以及拖拉机的初始位置(x,y)

接下来N行,每行包含一个干草捆的位置坐标(x,y)

输出格式

输出约翰需要移除的干草捆的最小数量。

数据范围

1N50000,

1x,y1000

输入样例:

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

 

解题思路

  首先先审题,题目给出的干草捆的坐标范围虽然是11000,但并不代表整个图的被限制在1000×1000的矩阵中,整个图其实是无限大的,点还可以走到1001以外的地方。类似于这样的效果。

  但事实上我们不可能去搜索一个无限大的图。因为障碍物的坐标范围为1x,y1000,因此我们可以给1000×1000的矩阵扩大一圈(扩大的一圈没有障碍物),01001,也就是只用搜1001×1001的矩阵就可以了。

  接着我们抽象出一个图的模型。首先,每一个格子就是一个点。边的话,就是每个格子上下左右四个方向,相邻的格子可以看作是有边相连的。然后不同的是这题要的并不是边权,而是点权(其实两者差不了多少)。如果一个格子是障碍物的话,那么这个格子的点权是1,否则是0

  然后我们可以把原问题转化为一个求最短路的问题。

  首先要证明这两个问题是等价的。对于任意一个从起点到终点的走法,原问题中移走障碍物的一种走法就对于一条路径,并且移走障碍物的数量就是这条路径的权值和。所以原问题的每一个方案就对应一条从起点到终点的路径,反过来每一条路径对应原问题的一个方案,而且路径长度就是移走障碍物的数量。所以要求原问题的最优解,等价于求最短路。

  当时写这题的话完全没有这种抽象的意识,完全没有去这么想,所以写不出来。

  然后我们发现,每条边的权重只有01。如果全部边的权重为1,我们可以用bfs求最短路。而只有01的话就用双端队列广搜。

  其实双端队列广搜本质就是dijkstra算法,不过由于边权只有01的特殊性,把堆变成了双端队列,时间复杂度为O(n)

  原理是,从一个点扩展的时候另一个点x的时候,如果边权为0,则把x插队头;如果边权为1,则把x插队尾。

  下面证明为什么可以用双端队列代替堆。

  首先是证明双端队列中所有的点到起点的距离大小最多只有两种,而且如果有两种的话他们的大小只相差1。比如,如果有两种不同的距离,那么双端队列前部分的点到起点的距离都是l,后部分的点到起点的距离都是l+1。如果只有一种距离,那么双端队列中的点到起点的距离都是l

 

  用归纳法。初始时,双端队列只有一个起点,显然成立。假设当双端队列的大小为n1时成立,证明当双端队列的大小为n时也成立。对于任意一个大小为n的双端队列,因为我们每次只能往队列插入一个元素,所以它是由大小为n1的双端队列扩展出来的。因为对于长度为n1的双端队列条件是成立的,所以到起点的距离大小不同的点最多只有两段。然后我们每次扩展都是从队头取出元素的,若从队头取出的元素到起点的距离为l,扩展的结果只有两种:一种是边权为0,扩展的结果就为l+0=l,插入队头;另一种是边权为1,扩展的结果就为l+1,插入队尾。插完后我们会发现,双端队列中还是最多只有两种不同的距离。前面是队列中有两种不同距离的证明,只有一种距离的证明同理。

  所以可以证明对于任何大小的双端队列,队列中所有的点到起点的距离大小最多只有两种,而且前一段的点到起点的距离为l,后一段的点到起点的距离为l+1

  也就意味着,任何时刻队列中所有的点,到起点的距离是升序的,所以队头的点到起点的距离一定是最小值,所以可以起到堆的效果。

  代码的写法和dijkstra的很像,只不过这里把优先队列改成了双端队列,边是根据曼哈顿距离来选择的。

  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/

posted @   onlyblues  阅读(50)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
Web Analytics
点击右上角即可分享
微信分享提示