[BZOJ1150][CTSC2007]数据备份Backup(贪心+堆+链表)
程序员文章站
2022-03-31 21:18:18
...
这题真是一环扣一环,做了好久好久,好题啊!
首先通过题意我们可以敏锐的捕捉到:虽然可以跨楼相连,但是这样是没有意义的,因为相邻的两个点相连一定比跨楼相连更优。
我们将楼与楼之间的距离求出来(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;
}