[APIO/CTSC 2007]数据备份(贪心+堆)

你在一家 IT 公司为大型写字楼或办公楼(offices)的计算机数据做备份。然而数据备份的工作是枯燥乏味的,因此你想设计一个系统让不同的办公楼彼此之间互相备份,而你则坐在家中尽享计算机游戏的乐趣。

已知办公楼都位于同一条街上。你决定给这些办公楼配对(两个一组)。每一对办公楼可以通过在这两个建筑物之间铺设网络电缆使得它们可以互相备份。

然而,网络电缆的费用很高。当地电信公司仅能为你提供 K 条网络电缆,这意味着你仅能为 K 对办公楼(或总计 2K 个办公楼)安排备份。任一个办公楼都属于唯一的配对组(换句话说,这 2K 个办公楼一定是相异的)。

此外,电信公司需按网络电缆的长度(公里数)收费。因而,你需要选择这 K对办公楼使得电缆的总长度尽可能短。换句话说,你需要选择这 K 对办公楼,使得每一对办公楼之间的距离之和(总距离)尽可能小。

下面给出一个示例,假定你有 5 个客户,其办公楼都在一条街上,如下图所示。这 5 个办公楼分别位于距离大街起点 1km, 3km, 4km, 6km 和 12km 处。电信公司仅为你提供 K=2 条电缆。

Solution

通过观察我们发现最优解只可能是两个相邻的点。

问题转化成了给一些点,选出其中m个使他们在互不相邻的前提下权值和最小。

这让我想到了一道题叫种树,然后发现一毛一样2333。

考虑选择一个点后,两边都不能选了,但最优解有可能属于两边。

我们从堆里取出最小的点,把两边的点删掉,用两边的点权之和减去当前点的权值最为这个点的权值放进堆里,继续贪心。

为什么这样是对的,因为当我贪心的选择了一个点后,两边的点要么都选,要么都不选,如果只选一个,还不如只选中间点呢。

注意因为要取最小值,所以注意判一下边界。

Code

#include<iostream>
#include<cstdio>
#include<queue>
#define mm make_pair
#define N 100002
using namespace std;
priority_queue<pair<long long,int> >q;
int n,k,l[N],r[N];
long long ans,a[N];
bool ji[N];
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;++i)scanf("%d",&a[i]);n--;
    for(int i=1;i<=n;++i){
       a[i]=a[i+1]-a[i],q.push(mm(-a[i],i));
       l[i]=i-1;r[i]=i+1;
     }
    a[0]=a[n+1]=0x7f7f7f7f; 
    for(int i=1;i<=k;++i){ 
      int x=q.top().second,y=q.top().first;q.pop();
      while(ji[x]){x=q.top().second;y=q.top().first;q.pop();}
      ans-=y;
      a[x]=a[l[x]]+a[r[x]]-a[x];
      q.push(mm(-a[x],x));
      ji[l[x]]=ji[r[x]]=1;
      l[x]=l[l[x]];r[l[x]]=x;
      r[x]=r[r[x]];l[r[x]]=x;
    }
    cout<<ans;
} 

猜你喜欢

转载自www.cnblogs.com/ZH-comld/p/9590804.html