騰訊校招編程題之快遞分身術

題目描述

城市裏有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,歡迎大佬們批評指正

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章