Skip to content

T20741: 两座孤岛最短距离

dfs, bfs, dijkstra, http://cs101.openjudge.cn/pctbook/T20741

给一个由1跟0组成的方形地图,1代表土地,0代表水域

相邻(上下左右4个方位当作相邻)的1组成孤岛

现在你可以将0转成1,搭建出一个链接2个孤岛的桥

请问最少要将几个0转成1,才能建成链接孤岛的桥。

题目中恰好有2个孤岛(顾答案不会是0)

输入

一个正整数n,代表几行输入 n行0跟1字串

输出

一个正整数k,代表最短距离

样例输入

3
110
000
001

样例输出

2

提示

样例输入中的两个孤岛最短距离为2

dfs + bfs

将第一个边缘作为多个起点,可以同时存入bfs的队列q中,由于这几个起点是同步开始搜索的,所以第一个搜到的一定对应最短值。

这道题目其实基本思路很好想,但是想要优化到不超时还是需要下功夫的。首先,只需要将找到的第一个孤岛打上标记,第二个不用管。其次,可以直接在找到第一个孤岛之后直接开始bfs,不用再次遍历列表了。

python
from collections import deque

def dfs(x, y, grid, n, queue, directions):
    """ Mark the connected component starting from (x, y) as visited using DFS. """
    grid[x][y] = 2  # Mark as visited
    queue.append((x, y))
    for dx, dy in directions:
        nx, ny = x + dx, y + dy
        if 0 <= nx < n and 0 <= ny < n and grid[nx][ny] == 1:
            dfs(nx, ny, grid, n, queue, directions)

def bfs(grid, n, queue, directions):
    """ Perform BFS to find the shortest path to another component. """
    distance = 0
    while queue:
        for _ in range(len(queue)):
            x, y = queue.popleft()
            for dx, dy in directions:
                nx, ny = x + dx, y + dy
                if 0 <= nx < n and 0 <= ny < n:
                    if grid[nx][ny] == 1:
                        return distance
                    elif grid[nx][ny] == 0:
                        grid[nx][ny] = 2  # Mark as visited
                        queue.append((nx, ny))
        distance += 1
    return distance

def main():
    n = int(input())
    grid = [list(map(int, input())) for _ in range(n)]
    directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
    queue = deque()

    # Start DFS from the first '1' found and use BFS from there
    for i in range(n):
        for j in range(n):
            if grid[i][j] == 1:
                dfs(i, j, grid, n, queue, directions)
                return bfs(grid, n, queue, directions)

if __name__ == "__main__":
    print(main())
python
from collections import deque


class Solution:
    def shortestBridge(self, grid) -> int:
        m, n = len(grid), len(grid[0])
        points = deque()

        def dfs(points, grid, m, n, i, j):
            if i < 0 or i == m or j < 0 or j == n or grid[i][j] == 2:
                return
            if grid[i][j] == 0:
                points.append((i, j))
                return

            grid[i][j] = 2
            dfs(points, grid, m, n, i - 1, j)
            dfs(points, grid, m, n, i + 1, j)
            dfs(points, grid, m, n, i, j - 1)
            dfs(points, grid, m, n, i, j + 1)

        flag = False
        for i in range(m):
            if flag:
                break
            for j in range(n):
                if grid[i][j] == 1:
                    dfs(points, grid, m, n, i, j)
                    flag = True
                    break

        x, y, count = 0, 0, 0
        while points:
            count += 1
            n_points = len(points)
            while n_points > 0:
                point = points.popleft()
                r, c = point[0], point[1]
                for k in range(4):
                    x, y = r + direction[k], c + direction[k + 1]
                    if x >= 0 and y >= 0 and x < m and y < n:
                        if grid[x][y] == 2:
                            continue
                        if grid[x][y] == 1:
                            return count
                        points.append((x, y))
                        grid[x][y] = 2
                n_points -= 1

        return 0


direction = [-1, 0, 1, 0, -1]

n = int(input())
grid = []
for i in range(n):
    row = list(map(int, list(input())))
    grid.append(row)

print(Solution().shortestBridge(grid))
python
import collections
def main():
    n=int(input())
    #grid=[[0]*(n+2)]
    grid=[]
    for i in range(n):
        p=list(int(x) for x in input())
        #p.insert(0,0)
        #p.append(0)
        grid.append(p)
    grid.append([0]*(n+2))
    visited = [[False for _ in range(n)] for _ in range(n)]
    dirs = [(-1, 0), (0, 1), (1, 0), (0, -1)]

    sr, sc = -1, -1
    for r in range(n):
        for c in range(n):
            if grid[r][c] == 1:
                sr, sc = r, c
                break
    q = collections.deque()
    q.append((sr, sc))
    visited[sr][sc] = True
    while q:
        r, c = q.popleft()
        for dr, dc in dirs:
            nr = r + dr
            nc = c + dc
            if 0 <= nr < n and 0 <= nc < n:
                if grid[nr][nc] == 1 and visited[nr][nc] == False:
                    visited[nr][nc] = True
                    q.append((nr, nc))

    #------------ 计算最短距离。多源bfs
    for r in range(n):
        for c in range(n):
            if visited[r][c] == True and grid[r][c] == 1:
                q.append((r, c))
    step = 0
    while q:
        curLen = len(q)
        for _ in range(curLen):
            r, c = q.popleft()
            for dr, dc in dirs:
                nr = r + dr
                nc = c + dc
                if 0 <= nr < n and 0 <= nc < n and visited[nr][nc] == False:
                    visited[nr][nc] = True
                    if grid[nr][nc] == 1:
                        return step
                    q.append((nr, nc))
        step += 1
    return step
print(main())

思路:这题时间卡的超极严。。。简直是不能做任何多的一点操作,要用Dijkstra算法并且还要最简化。权值判断即为从1到1为0,1到0和0到0为1。

用 Dijkstra算法时,由于每次处理的都是当前步数最小的,说明刚开始是先处理同一个岛上的点,假如出现从 0 到 1 的情况时,说明已经到另一个岛上了,可以直接返回当前步数。

python
# 俞天麒 24物理学院
import heapq
n=int(input())
land=[]
dir=[[0,1],[0,-1],[1,0],[-1,0]]
for i in range(n):
    land.append(input())
visited=[[True]*(len(land[i])) for i in range(len(land))]
def find(x1,y1):
    pos=[]
    heapq.heappush(pos,(0,x1,y1))
    visited[x1][y1]=False
    while pos:
        step,x,y=heapq.heappop(pos)
        #print(step,x,y)
        if land[x][y]=="1" and step!=0:
            return step
        for dx,dy in dir:
            nx,ny=x+dx,y+dy
            if 0<=nx<n and 0<=ny<len(land[nx]) and visited[nx][ny]:
                visited[nx][ny]=False
                if land[nx][ny]==land[x][y] and land[x][y]!="0":
                    heapq.heappush(pos,(step,nx,ny))
                else:
                    heapq.heappush(pos,(step+1,nx,ny))
mark=False
for i in range(n):
    for j in range(len(land[i])):
        if land[i][j]=="1":
            print(find(i,j)-1)
            exit()
python
#吴迪、24物理学院
"""
Dijstra算法的短代码优化版本,其实没有必要把所有1都找出来,只要找到一个1,以之为出发点,
遇到1步数不变,遇到0步数+1,这样同一个岛内的step都是一样的,一个岛就相当于一个点
"""
n=int(input())
ma=[list(map(int,input())) for _ in range(n)]
m,dire=len(ma[0]),[(1,0),(-1,0),(0,1),(0,-1)]
for i in range(n):
    for j in range(m):
        if ma[i][j]==1:
            x1,y1=i,j
import heapq
def dijkstra(x1,y1):
    q,visited=[],[[False]*m for _ in range(n)]
    heapq.heappush(q,(0,x1,y1))
    while q:
        step,x,y=heapq.heappop(q)
        if visited[x][y]:
            continue
        visited[x][y] = True
        if ma[x][y]==1 and step!=0:
            return step
        for dx,dy in dire:
            if 0<=x+dx<n and 0<=y+dy<m and not visited[x+dx][y+dy]:
                heapq.heappush(q,(step+1-ma[x+dx][y+dy],x+dx,y+dy))
print(dijkstra(x1,y1))

思路:一个bfs找第一个岛,另一个从第一个岛找第二个岛。基本都是按套路来,只是visited可以共享,感觉是唯一一个优化

python
# 王梓航,24物理学院
import heapq
dir = [(-1,0),(1,0),(0,1),(0,-1)]
n = int(input())
a = [list(map(int,list(input()))) for _ in range(n)]
m = len(a[0])
first=set([])
for i in range(n):
    for j in range(m):
        if a[i][j]==1:
            visited=[[False]*m for _ in range(n)]
            visited[i][j]=True
            queue=[]
            heapq.heappush(queue,(i,j))
            first.add((i,j))
            while queue:
                x,y=heapq.heappop(queue)
                for dx,dy in dir:
                    px,py=x+dx,y+dy
                    if 0<=px<n and 0<=py<m and not visited[px][py] and a[px][py]==1:
                        visited[px][py]=True
                        first.add((px,py))
                        heapq.heappush(queue,[px,py])
            queue = []
            for p, q in first:
                heapq.heappush(queue, [0, p, q])
            while queue:
                count, x, y = heapq.heappop(queue)
                count += 1
                for dx, dy in dir:
                    px, py = x + dx, y + dy
                    if 0 <= px < n and 0 <= py < m and not visited[px][py]:
                        visited[px][py] = True
                        heapq.heappush(queue, [count, px, py])
                        if a[px][py] == 1:
                            print(count-1)
                            exit(0)

先通过一次DFS遍历第一座孤岛,存储其所有点的坐标,随后使之“沉没”。在扫描到陆地(必属于第二座孤岛)后,计算其与之前存储的所有点间最短路径的长度,取最小值即可。

c++
// 周晟昱 24工学院
#include <algorithm>
#include <cmath>
#include <iostream>
#include <utility>
#include <vector>

int n, ans = 1000000000;
std::vector<std::vector<int>> mp;
std::vector<std::pair<int, int>> land1;

void build_island(const int &x, const int &y)
{
    if (mp[x][y] == 0)
        return;
    land1.push_back(std::make_pair(x, y));
    mp[x][y] = 0;
    if (x - 1 >= 0)
        build_island(x - 1, y);
    if (x + 1 < n)
        build_island(x + 1, y);
    if (y - 1 >= 0)
        build_island(x, y - 1);
    if (y + 1 < n)
        build_island(x, y + 1);
}

int main()
{
    char c;
    std::cin >> n;
    mp.resize(n, std::vector<int>(n, 0));
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < n; ++j)
        {
            std::cin >> c;
            mp[i][j] = c - '0';
        }
    bool flag = true;
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < n; ++j)
            if (mp[i][j])
            {
                if (flag)
                {
                    build_island(i, j);
                    flag = false;
                }
                else
                    for (auto &[a, b] : land1)
                        ans = std::min(ans, std::abs(i - a) + std::abs(j - b) - 1);
            }
    std::cout << ans << std::endl;
    return 0;
}

思路:

  • 学语法千日用语法一时,yield表达式真好使
  • 把两个岛的边界找出来,存起来,找曼哈顿距离最小值,可过
  • 可以转成集合避免重复计算耽误时间
python
# 颜鼎堃 24工学院
DIRECTIONS = ((0, 1), (0, -1), (1, 0), (-1, 0))
def dfs(x, y):
    stack = [(x, y)]
    while stack:
        x1, y1 = stack.pop()
        isl[x1][y1] = "0"
        for dx, dy in DIRECTIONS:
            nx, ny = x1 + dx, y1 + dy
            if isl[nx][ny] == "1":
                stack.append((nx, ny))
            else:
                yield (x1, y1)
                    
                
n = int(input())
isborder = []
cnt = 0
isl = [["0" for i in range(n+2)]] + [["0"] + list(input()) + ["0"] for i in range(n)] + [["0" for i in range(n+2)]]
for i in range(1, 1 + n):
    while "1" in isl[i]:
        cnt += 1
        isborder.append(set(dfs(i, isl[i].index("1"))))
        if cnt == 2:
            break
    if cnt == 2:
        break
print(min([abs(a - c) + abs(b - d) - 1 for a, b in isborder[0] for c, d in isborder[1]]))