1005 航线(2025“钉耙编程”中国大学生算法设计春季联赛(1))

航线(2025“钉耙编程”中国大学生算法设计春季联赛(1))

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

题目大意:

染染船长在一次航行中,来到了一个复杂的海洋区域。这个海洋区域可以被视为一个二维网格,每个网格点代表一个海域,船需要从左上角(1, 1)出发,最后到达右下角(n, m)。在网格中,每个格点有两个花费:

  1. t[i][j]:表示直接通过该海域所需要的时间。
  2. d[i][j]:表示如果船在通过该海域时需要转向,那么需要的额外时间。

船一开始朝右驶出,目标是从起始位置(1, 1)驶到目标位置(n, m)。沿途的航行可能需要转向,每次转向都会增加额外的时间开销。转向的方向包括向左、向右、向上和向下,转向都会增加d[i][j]的花费。我们需要计算出从(1, 1)到(n, m)的最短时间。

输入:

  1. 一个整数T表示测试数据组数。
  2. 对于每组数据:
    • 两个整数nm,分别表示海洋的行数和列数。
    • 接下来有n行,每行有m个整数,表示直接通过每个海域所需要的时间t[i][j]
    • 接下来有n行,每行有m个整数,表示如果需要转向所需的额外时间d[i][j]

输出:

对于每组数据,输出一个整数,表示从(1, 1)到(n, m)的最短时间。

样例输入:

2
1 1
1
1
3 3
1 1 1
1 2 1
1 1 1
0 999 999
0 0 999
999 0 0

样例输出:

2
6

题解:

本题是一个典型的最短路径问题,结合了网格的结构和转向的额外费用。可以通过广度优先搜索(BFS)或者Dijkstra算法来求解。

我们选择使用Dijkstra算法,因为网格的每个格点存在多条路径,每条路径的花费可能会因为转向而增加。Dijkstra算法能够有效地处理图中带有不同边权的最短路径问题。

解题步骤:
  1. 建模和状态表示

    • 我们可以把海洋看作一个图,其中每个海域(i, j)是一个节点。每个节点之间的边代表着船在不同方向上移动的时间。通过考虑船是否需要转向,边的权重(即时间消耗)可以增加。
    • 每个节点的状态由(i, j, direction)来表示,其中direction为船当前的朝向。我们可以定义4个方向:右(0)、下(1)、左(2)、上(3)。
  2. 初始化

    • 我们需要一个距离数组dist[i][j][direction]来表示从起始位置到达节点(i, j)并且朝向direction的最短时间。
    • 初始化起始位置(1, 1)的状态:船开始时朝右(方向为0),因此其距离为dist[0][0][0] = 0
  3. Dijkstra算法

    • 使用优先队列(最小堆)来保存当前的状态。每次取出堆顶的元素,更新该状态的邻居节点的最短时间。
    • 对于每个方向,计算从当前位置到达其邻居节点的时间。考虑转向的额外费用。
    • 如果通过某个方向转移到新的位置的时间比当前记录的时间要小,则更新该状态,并将新的状态加入队列。
  4. 处理终点

    • 终点是右下角的海域( n-1, m-1 )。在到达该海域时,我们需要考虑其相邻位置的不同方向。如果是从右下角向下移出海洋,则结束计算。
  5. 复杂度

    • 每个位置的状态有4种可能的方向,因此总共有n * m * 4个状态,使用Dijkstra算法进行优化时,时间复杂度为O((n * m * 4) log(n * m * 4))

代码实现:

#include <bits/stdc++.h>
using namespace std;

using ll = long long;
const ll INF = LLONG_MAX;

const vector<pair<int, int>> dirs = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    int T;
    cin >> T;
    while (T--)
    {
        int n, m;
        cin >> n >> m;

        vector<vector<ll>> t(n, vector<ll>(m));
        for (int i = 0; i < n; ++i)
        {
            for (int j = 0; j < m; ++j)
            {
                cin >> t[i][j];
            }
        }

        vector<vector<ll>> d(n, vector<ll>(m));
        for (int i = 0; i < n; ++i)
        {
            for (int j = 0; j < m; ++j)
            {
                cin >> d[i][j];
            }
        }

        vector<vector<vector<ll>>> dist(n, vector<vector<ll>>(m, vector<ll>(4, INF)));
        priority_queue<tuple<ll, int, int, int>, vector<tuple<ll, int, int, int>>, greater<>> pq;

        dist[0][0][0] = 0;
        pq.emplace(0, 0, 0, 0);

        ll ans = INF;

        while (!pq.empty())
        {
            auto [time_so_far, i, j, dir_in] = pq.top();
            pq.pop();

            if (time_so_far > dist[i][j][dir_in])
            {
                continue;
            }


            if (i == n - 1 && j == m - 1)
            {
                int k = 1;
                ll cost = t[i][j];
                if (dir_in != k)
                {
                    cost += d[i][j];
                }
                ans = min(ans, time_so_far + cost);
            }

            for (int k = 0; k < 4; ++k)
            {
                auto [dx, dy] = dirs[k];
                int ni = i + dx;
                int nj = j + dy;

                ll cost = t[i][j];
                if (dir_in != k)
                {
                    cost += d[i][j];
                }
                ll new_time = time_so_far + cost;

                bool is_inside = (ni >= 0 && ni < n && nj >= 0 && nj < m);
                if (is_inside)
                {
                    if (new_time < dist[ni][nj][k])
                    {
                        dist[ni][nj][k] = new_time;
                        pq.emplace(new_time, ni, nj, k);
                    }
                }
                else
                {
                    if (i == n - 1 && j == m - 1 && k == 1)
                    {
                        ans = min(ans, new_time);
                    }
                }
            }
        }

        cout << ans << '\n';
    }

    return 0;
}

代码解释:

  • 输入解析:先输入T,表示数据组数。每组数据包括nm,接着输入两组矩阵分别表示直线通过时间t和转向时间d
  • 优先队列:使用priority_queue来执行Dijkstra算法,每次从队列中取出最小时间并进行状态更新。
  • 状态更新:每次从当前格点开始,尝试移动到相邻的4个方向,并计算移动的时间。如果移动后的时间更短,就更新状态并将新的状态放入队列中。

时间复杂度:

  • 每次更新都要进行O(log(n * m * 4))的操作,因此总体复杂度为O((n * m * 4) log(n * m * 4)),对于每组数据,这个复杂度是可以接受的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值