Returning Home(建图、最短路)

题意

给定一个 n ∗ n n*n nn的网格,在这个网格中给出两个坐标,分别作为起点和终点。并且在图中存在 m m m个传送阵,传送阵的坐标已知。从起点开始向终点走,相邻两个位置之间花费一个单位,如果走到与某个传送阵同行或者同列,那么可以不耗花费的传送到那个传送阵的位置。问从起点到终点,最少花费多少单位。

数据范围

1 ≤ n ≤ 1 0 9 1 \leq n \leq 10^9 1n109
0 ≤ m ≤ 1 0 5 0 \leq m \leq 10^5 0m105

思路

如果传送阵直接建一个完全图,那么显然会爆空间和时间,因此需要考虑更简洁的建图方式。
首先我们可以考虑到,两个传送阵之间的距离为 m i n ( x 2 − x 1 , y 2 − y 1 ) min(x_2 - x_1, y_2 - y_1) min(x2x1,y2y1),这就等价于两个传送阵在同一行或者同一列上。因此,我们可以对传送阵的横坐标排序,相邻两个连起来;再对传送阵的纵坐标排序,相邻两个连起来。因为要求的是最短路,所以两点之间出现重边也没关系。
最后,将起点作为 0 0 0号点,终点作为 m + 1 m+1 m+1号点,起点向每一个传送阵连一条边,终点向每个传送阵连一条边,起点和终点之间再连一条边。值得注意的是,起点到传送阵的距离计算方式和传送阵到终点的距离计算方式是不同的,因为一个可以传送,另一个不能传送。
这样图就建好了,然后跑一个Dijkstra算法就完成了。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <cmath>

using namespace std;

typedef long long ll;
typedef pair<ll,int> pli;
typedef pair<int,ll> pil;

const int N = 100010, M = 10 * N;
const ll inf = 1e18;

int sz,n,S,T;
int h[N], e[M], ne[M], idx;
ll w[M];
ll xx[N], yy[N];
ll dist[N];
bool st[N];
pli x_id[N], y_id[N];

void add(int a,int b,ll c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

void dijkstra()
{
    for(int i=0;i<=n+1;i++) dist[i] = inf;
    memset(st,0,sizeof(st));
    dist[0] = 0;
    priority_queue<pli,vector<pli>,greater<pli> > heap;
    heap.push({dist[0],0});
    while(heap.size()){
        auto t = heap.top();
        heap.pop();
        int ver = t.second;
        ll distance = t.first;
        if(st[ver]) continue;
        st[ver] = true;
        for(int i=h[ver];~i;i=ne[i]){
            int j = e[i];
            if(dist[j]>distance+w[i]){
                dist[j] = distance + w[i];
                heap.push({dist[j],j});
            }
        }
    }
}

int main()
{
    cin >> sz >> n;
    memset(h,-1,sizeof(h));
    ll sx,sy,fx,fy;
    cin >> sx >> sy >> fx >> fy;
    ll dd = abs(sx - fx) + abs(sy - fy);
    add(0,n+1,dd), add(n+1,0,dd);
    for(int i=1;i<=n;i++){
        cin >> xx[i] >> yy[i];
        x_id[i] = {xx[i],i}, y_id[i] = {yy[i],i};
    }
    for(int i=1;i<=n;i++){
        ll d = min(abs(sx-xx[i]),abs(sy-yy[i]));
        add(0,i,d), add(i,0,d);
    }
    for(int i=1;i<=n;i++){
        ll d = abs(fx-xx[i]) + abs(fy-yy[i]);
        add(n+1,i,d), add(i,n+1,d);
    }
    sort(x_id+1,x_id+n+1);
    sort(y_id+1,y_id+n+1);
    for(int i=1;i<n;i++){
        int id1 = x_id[i].second, id2 = x_id[i+1].second;
        ll d = abs(x_id[i].first - x_id[i+1].first);
        add(id1,id2,d), add(id2,id1,d);
    }
    for(int i=1;i<n;i++){
        int id1 = y_id[i].second, id2 = y_id[i+1].second;
        ll d = abs(y_id[i].first - y_id[i+1].first);
        add(id1,id2,d), add(id2,id1,d);
    }
    dijkstra();
    cout << dist[n+1] << endl;
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值