[BZOJ1150][CTSC2007]数据备份Backup(贪心+堆+链表)

传送门


  这题真是一环扣一环,做了好久好久,好题啊!
  首先通过题意我们可以敏锐的捕捉到:虽然可以跨楼相连,但是这样是没有意义的,因为相邻的两个点相连一定比跨楼相连更优。
  我们将楼与楼之间的距离求出来(n-1个数)那么问题就转化为:在n-1个数中选k个数使得他们相加起来最小,且选了一个数后相邻的两个数不可以再选。
  那也就是说,我们需要开一个小根堆维护最小值minn,如果选择了当前的最小值minn,那么最小值两边的值都不可以选,那么如果选了两边的反而更优呢??这里就把我难住了,其实啊,这题最精妙的部分就在于此。我们可以回想一下网络流“反向边退流”的思路,当我们发现这样子不优的时候可以通过反向边流回去,那么这题也与之同类,我们也可以设置一个“反向边”来反悔。
  那么如何设置反向边呢?我们往堆里加进一个新点q,设minn左边的点权值为prev,右边的点权值为next,那么q的权值就为prev+next-minn,为什么?如果我们在往后的决策中选到q这个元素,然后把它加进ans里面,那么我们之前加的minn的影响也随之删除了(minn+prev+next-minn=prev+next),取而代之的是minn两边的prev和next,成功完成了反悔的过程。
  具体过程:
  取出最小值1,ans+=1
  将他两边的2、3也取出,同时新加入一个4=2+3-1
  假如我们下次选到4,假设现在ans还是=1,那么ans=1+4=1+(2+3-1)=2+3,成功反悔。
  这里写图片描述
  

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<queue>
#include<vector>
using namespace std;
const int N=200010;
const int INF=(1<<30);
int n,k;
int pos[N];
struct heap
{
    int val,prev,next,k,pos;
    friend bool operator > (heap n1,heap n2){return n1.val>n2.val;}
}a[N]; int tot=0;
int w[N]; bool v[N];
priority_queue<heap , vector<heap> , greater<heap> >q;

int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
        scanf("%d",&w[i]);
    a[0].prev=a[0].next=a[0].k=0; a[0].val=INF;
    for(int i=1;i<n;i++)
    {
        int x=w[i+1]-w[i];
        a[i].val=x;
        a[i].pos=i;
        if(i!=1) a[i].prev=i-1;
        if(i!=n-1) a[i].next=i+1;
        a[i].k=1;
        q.push(a[i]);
    }
    n--; tot=n;
    long long ans=0;
    memset(v,false,sizeof(v));
    while(k>0)
    {
        heap minn=q.top();
        q.pop();
        int pos=minn.pos,prev=a[pos].prev,next=a[pos].next;
        if(v[pos]) continue;
        v[pos]=v[prev]=v[next]=true;
        ans+=a[pos].val;
        k-=a[pos].k;
        //push(q=prev+next-p)
        tot++;
        a[tot].pos=tot; 
        a[tot].val=a[prev].val+a[next].val-a[pos].val;
        a[tot].k  =a[prev].k + a[next].k - a[pos].k;
        a[tot].prev=a[prev].prev; a[tot].next=a[next].next;
        a[a[prev].prev].next=tot;
        a[a[next].next].prev=tot;
        q.push(a[tot]);
    }
    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值