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