腾讯校招编程题之快递分身术

题目描述

城市里有3000条横向的道路和3000条纵向的道路,分别从1开始编号知道3000,。相邻两条平行道路的间距为1。

我们用(x,y)表示第x号横向道路和第y号纵向道路。N个需要送快递的点坐落在这些交点上。小Q只能沿着这些道路送快递,而且只能在道路的交叉点改变方向。

接下来的M天,小Q每天都要从指定的地方出发将快递送往N个点。幸运的是,犇犇老师教了小Q分身大法,可以消耗s点法力值制造一个可以走s距离的分身,但是每个分身只能搬得动一件快递。小Q不想自己去送快递,于是他想请你帮忙计算,每一天他最少需要多少法力值才能让分身们帮他送完所有的快递。

输入描述

每个输入包含一个测试用例。

每个测试用例的第一行包含两个正整数,表示需要送快递的点数N(N<=200000)和小Q送快递的天数M(M<=200000)。

接下来的N行,每行包含两个正整数xi(xi<=3000)和yi(yi<=3000),表示需要送快递的点的坐标。

接下来的M行,每行包含两个正整数ai(ai<=3000)和bi(bi<=3000),表示当天送快递的出发点。

输出描述

对于每一天,在单独的一行输出一个整数,表示当天送完快递最少需要的法力值

样例输入

3 3
5 5
5 10
10 5
1 1
5 5
10 10

样例输出

34
10
20

题意分析

方案一:在每一天,以出发点为坐标中心,遍历所有收货点进行路径长度的计算

评价一:这样的算法规模太大,时间复杂度为O(MN),故该方案可以直接抛弃

方案二:在输入收货点的时候,计算所有的点与原点的路径长度,最后根据每天的出发点,减去与原点之间的差值,算出最少小号的法力

评价二:这样的算法规模得到优化,时间复杂度为O(M+N),但该算法存在缺陷,所有的收货点必须位于出发点的同一象限之内

方案三:在输入收货点的时候,计算所有的点与原点的的路径长度。接着预计算一个距离表(具体意义看下文),最后通过某条公式直接算出最少小号的法力

评价三:这样的算法时间复杂度为O(M+N),兼容了多个象限上的路程问题,推荐使用

分析过程

我想你们一定会很好奇,我是怎么想到这第三个方案的。首先,直觉告诉我,这个最短路径,肯定与所有点到原点的路程之和存在一定关系。那么,现在就让我们见证下Excel强大的分析功能(为方便说明,下图只抽取y轴上的距离进行计算)

从图中我们可以发现,计算距离与实际距离是相同的,而且距离偏差存在一定的规律可巡,证明该条公式是存在的。

计算公式

这是我的猜想:法力消耗=sum-p*count+d[p],因为不知道怎么证明

参数说明

sum:一个整数,表示所有收货点在某一方向距离原点的路径长度

p:一个整数,表示送货点在某一方向上的位置

count:一个整数,表示收货点的数量

d:一个数组,下标代表在某一方向上的位置,表示在一个方向中,每个点距离所有收货点的距离补偿

距离偏差的深究

仔细研究这个d数列可以发现,他是一个由偶数组成的数列,并且在每当越过一个收货点,距离偏差都会加上2,同时这个距离偏差是可以累加的。有以下两条公式可以参考一下:

距离偏差=距离偏差+当前所在位置跨越的点数*2。

距离补偿[n]=距离补偿[n-1]+当前距离偏差

关于这个距离补偿的数列,我不清楚存在该结构的原因,而且不清楚距离偏差为什么那样计算。如果有知道其中原因的,麻烦告知

参考代码

#include<iostream>
#include<map>
using namespace std;

void initDistance(map<int, int> point, int distance[]) {
    map<int, int>::iterator iter = point.begin();
    for (int i = 1, offset = 0; i <= 3000; i++) {
        distance[i] = distance[i - 1] + offset;
        if (i == iter->first) {
            offset += 2 * iter->second;
            iter++;
        }
    }
}

int main() {
    int pointCount, dayCount, x, y;
    int sum_x = 0, sum_y = 0;
    int distance_x[3001] = {0};
    int distance_y[3001] = {0};
    map<int, int> point_x, point_y;
    cin >> pointCount >> dayCount;
    for (int i = 0; i < pointCount; i++) {
        cin >> x >> y;
        sum_x += x;
        sum_y += y;
        point_x[x] += 1;
        point_y[y] += 1;
    }
    initDistance(point_x, distance_x);
    initDistance(point_y, distance_y);
    for (int i = 0; i < dayCount; i++) {
        cin >> x >> y;
        int count_y = sum_y - y * pointCount + distance_y[y];
        int count_x = sum_x - x * pointCount + distance_x[x];
        cout << count_x + count_y << endl;
    }
    return 0;
}

算法评测

由于是昨天刚出的编程题,当时只想到第二个方案,只过了30%的数据,今天才想出最好的方案。但是考试链接已失效,我也没法验证这个代码是否能AC,欢迎大佬们批评指正

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值