hihocoder #1138 : Islands Trave

  这道题一开始想的是用Dijkstra或者是SPFA算法来写,写好了以后提交,结果TLE了。当时写的时候就感觉每个节点更新的复杂度至少是 O(n) 级别的,再考虑到数据量有 N<=1000000<=Xi,Yi<=1000000000 就感觉会超时,不过当时也不知道该怎么降维,只能硬着头皮上了。后来看了hihocoder上面的题解后

http://www.hihocoder.com/discuss/question/2792

知道了怎么将 O(n) 降为 O(4) ,重新写了以后,就AC了。
  首先我来讲讲 O(n) 的算法是个什么思路,不想看的朋友可以直接看下一段。在SPFA算法中,在某个节点 i 从队列中弹出来后,我们需要将跟这个节点相连的节点最短路径进行更新,然后将路径长度变短的节点重新塞入队列中。这样一来,由于每个节点都可以跟节点i相连,所以需要将所有的节点都进行一遍更新,也就是每个节点的更新复杂度是 O(n) ,在这样大的数据量的情况下,跑完整个list肯定就会超时了。因此现在就需要降维。
  看了hihocoder的题解,简单来说,就是分别找到在X和Y上距离每个节点最近的两个节点(一前一后),然后在每次更新相邻节点时,就只用更新前后4个,也就是 O(4) 的复杂度了。因此我们需要对X进行排序,找到该节点的一前一后两个点(X数值相同也行);同理对Y进行处理。之后每次更新只用找到这4个点更新就行。为了更进一步进行优化,我还做了剪枝处理,设置一个1到N的距离最大值maxDistance,如果两个点的距离大于这个值,就直接丢弃,不需要在访问了。并且需要对maxDistance进行维护,更新。下面就是AC代码:
  

//Islands Trave
#include<iostream>
//#include<map>
//#include<string>
#include<queue>
#include<vector>
#include <cstdio>
#include<fstream>
#include<algorithm>
using namespace std;

#define Min(a,b) a<b?a:b
#define INF 1000000005
#define Num 100000+5
typedef struct {//每个节点的结构
    int X;
    int Y;
    int ID;
    int LinkNode[4];//分别对应X上的一前一后和Y上的一前一后,beforeX, afterX,beforeY, afterY 
} Location;

Location location[Num];
int head[Num];
queue<int> list;
bool visit[Num];
int Distance[Num];

int differ(int l1, int l2)//计算两个节点的距离
{
    if (l1 == l2) return INF;
    return Min(abs(location[l1].X - location[l2].X), abs(location[l1].Y - location[l2].Y));
}

bool compx(Location l1, Location l2)///按照X对节点进行排序
{
    if (l1.X != l2.X) return l1.X < l2.X;
    else return  l1.Y < l2.Y;
}

bool compy(Location l1, Location l2)//按照Y对节点进行排序
{
    if (l1.Y != l2.Y) return l1.Y < l2.Y;
    else return  l1.X < l2.X;
}

bool compID(Location l1, Location l2)//按照ID对节点进行排序
{
    return l1.ID < l2.ID;
}

int LuShi()
{
    int N = 0, maxDistance = 1000000000;
    cin >> N;
    int i = 0, j = 0;
    for (i = 1; i < N + 1; i++)
    {
        cin >> location[i].X >> location[i].Y;
        location[i].ID = i;
        visit[i] = false;
        Distance[i] = INF;
    }

    sort(location + 1, location + N + 1, compx);//对X进行排序
    i = 1;
    location[i].LinkNode[0] = 0;
    while (i<N + 1)
    {
        j = i + 1;
        location[j].LinkNode[0] = location[i].ID;//beforeX,在X上的前一个
        location[i].LinkNode[1] = j <= N ? location[j].ID : 0;//afterX,在X上的后一个
        i++;
    }

    sort(location + 1, location + N + 1, compy);//对Y进行排序
    i = 1;
    location[j].LinkNode[2] = 0;
    while (i < N + 1)
    {
        j = i + 1;
        location[j].LinkNode[2] = location[i].ID;//beforeY,在Y上的前一个
        location[i].LinkNode[3] = j <= N ? location[j].ID : 0;//afterY,在Y上的后一个
        i++;
    }

    sort(location + 1, location + N + 1, compID);//恢复原来的顺序,方便查找

    maxDistance = differ(1,N);//计算最大值,准备剪枝
    for (int i = 1; i < N + 1; i++)//对1节点进行更新,将周围4个节点塞入队列
    {
        if (differ(1, i)<maxDistance) {
            Distance[i] = differ(1, i);
            list.push(i);
            visit[i] = true;
        }
    }
    while (!list.empty())
    {
        for (i = 0; i < 4; i++)//可以看到,这个复杂度是O(4)
        {
            int to = location[list.front()].LinkNode[i];//相邻的节点
            int min = differ(list.front(), to) + Distance[list.front()];//计算1到该节点的最短距离
            if (min<maxDistance) {//如果这一跳距离大于1到N的距离,那么最后通过这个路径到达N的距离一定大于等于maxDistance,剪枝
                if (min < Distance[to]) {//只有小于原来的距离才剪枝
                    if (to == N) {//如果相邻节点是N,更新最短距离maxDistance
                        maxDistance = min;
                    }
                    else//不是到N的,将这个节点塞入队列
                    {
                        if (visit[to] == false) {//如果在队列中,就不需要塞了
                            list.push(to);
                            visit[to] = true;
                        }
                    }
                    Distance[to] = min;//更新相邻节点的最短距离
                }
            }
        }
        visit[list.front()] = false;//弹出节点
        list.pop();
    }
    cout << Distance[N] << endl;//直接输出N的值
    return 0;
}

int main()
{
    streambuf * oldbuf = cin.rdbuf((new ifstream("C:\\Users\\yzc\\Desktop\\input.txt"))->rdbuf());//重定向,OJ时将它注释掉
    //cout << LuShi() << endl;
    LuShi();
    system("pause");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值