[算法競賽進階指南] 數據備份 (優先隊列+雙向鏈表)

題目

你在一家IT公司爲大型寫字樓或辦公樓的計算機數據做備份。

然而數據備份的工作是枯燥乏味的,因此你想設計一個系統讓不同的辦公樓彼此之間互相備份,而你則坐在家中盡享計算機遊戲的樂趣。

已知辦公樓都位於同一條街上,你決定給這些辦公樓配對(兩個一組)。

每一對辦公樓可以通過在這兩個建築物之間鋪設網絡電纜使得它們可以互相備份。

然而,網絡電纜的費用很高。

當地電信公司僅能爲你提供 K 條網絡電纜,這意味着你僅能爲 K 對辦公樓(總計2K個辦公樓)安排備份。

任意一個辦公樓都屬於唯一的配對組(換句話說,這 2K 個辦公樓一定是相異的)。

此外,電信公司需按網絡電纜的長度(公里數)收費。

因而,你需要選擇這 K 對辦公樓使得電纜的總長度儘可能短。

換句話說,你需要選擇這 K 對辦公樓,使得每一對辦公樓之間的距離之和(總距離)儘可能小。

下面給出一個示例,假定你有 5 個客戶,其辦公樓都在一條街上,如下圖所示。

這 5 個辦公樓分別位於距離大街起點 1km, 3km, 4km, 6km 和 12km 處。

電信公司僅爲你提供 K=2 條電纜。

圖片

上例中最好的配對方案是將第 1 個和第 2 個辦公樓相連,第 3 個和第 4 個辦公樓相連。

這樣可按要求使用 K=2 條電纜。

第 1 條電纜的長度是 3km-1km=2km ,第 2 條電纜的長度是 6km-4km=2km。

這種配對方案需要總長 4km 的網絡電纜,滿足距離之和最小的要求。

輸入格式

第一行輸入整數n和k,其中 n 表示辦公樓的數目,k 表示可利用的網絡電纜的數目。

接下來的n行每行僅包含一個整數s,表示每個辦公樓到大街起點處的距離。

這些整數將按照從小到大的順序依次出現。

輸出格式

輸出應由一個正整數組成,給出將2K個相異的辦公樓連成k對所需的網絡電纜的最小總長度。

數據範圍

2≤n≤100000,
1≤k≤n/2,
0≤s≤1000000000

輸入樣例:
5 2 
1
3
4
6
12
輸出樣例:
4
分析
  • 這道題目我們首先很容易發現性質要選最小的一些值,最優解中每兩個配對的辦公樓一定是相鄰的,既然如此的話不妨設置一個數組D,D[i]表示第i個辦公樓和第i-1個辦公樓之前的距離.然後我們發現,如果說當前D數組中最小值爲D[i],那麼會有兩種情況產生.
  1. 選擇了D[i],那麼D[i-1]和D[i+1]都不能選擇了
  2. 選擇了D[i+1]和D[i-1],然後無法選擇D[i].
  • 分析上面兩個方案,我們發現這是唯一的兩個方案,也就是最優解,最小值的左右兩側的數,要麼都選擇,要麼都不選擇.
  • 既然如此的話,我們可以可以先選則D數列中的最小值,然後把D[i-1],D[i],D[i+1]從D數列中刪除,然後我們再在這個位置插入D[i-1]+D[i+1]-D[i]也就是說,選擇左右兩個位置不選擇D[i]這種方案,於是這樣,我們就成功的讓這兩個方案都加入到了優先隊列之中.
  • 至於如何維護這個位置的話,我們可以使用專門維護前驅和後繼的鏈表.
代碼
#include <iostream>
#include <algorithm>
#include <set>
using namespace std;
typedef long long LL;
typedef pair<LL,int> PLI;
const int N=1e5+5;

int n,k;
int l[N],r[N];
LL d[N];

void delete_node(int x){
	r[l[x]]=r[x];
	l[r[x]]=l[x];
}

int main()
{
	ios::sync_with_stdio(false);
	cin>>n>>k;
	for(int i=0;i<n;i++) cin>>d[i];
	for(int i=n-1;i;i--) d[i]-=d[i-1];
	
	set<PLI> s;
	d[0] = d[n] = 1e15;
	for(int i=0;i<=n;i++) {
		s.insert({d[i],i});
		l[i]=i-1;
		r[i]=i+1;
	}
	
	LL ans=0;
	while(k--){
		auto x=s.begin();
		LL v=x->first;
		int p=x->second,left=l[p],right=r[p];
		
		ans+=v;
		
		s.erase(x);
		s.erase({d[left],left});
		s.erase({d[right],right});
		delete_node(left);
		delete_node(right);
		
		d[p]=d[left]+d[right]-d[p];
		s.insert({d[p],p});
	}
	cout<<ans<<endl;
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章