杂题选讲 1 [APIO/CTSC 2007]数据备份 luogu紫题首祭
题目传送门
先闲扯一波,今天是2021年4月13日星期二,我是2021年2月25日开始学信息竞赛,
目前是全机房最菜,这是经过我的努力抄题解,终于做抄出了自己首道luogu紫题,
算是纪念一下吧。
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=100006;
struct lb{ //用数组模拟链表
int prv,nxt,d,th;
}p[N];
struct d{ //小根堆
int D,tp;
}h[N];
int tot;
void up(int i) //小根堆上调操作
{
while(i>1)
{
if(h[i].D<h[i>>1].D){
swap(h[i],h[i>>1]);
swap(p[h[i].tp].th,p[h[i>>1].tp].th); //在链表中也需要调整,但只需要对换编号就行
i >>=1;
}
else return ;
}
}
void down(int i)//// //小根堆下调操作
{
int ii= i << 1; //ii为儿子
while(ii<=tot)
{
if(ii<tot&&h[ii].D>h[ii+1].D)++ii;
if(h[ii].D<h[i].D)
{
swap(h[ii],h[i]);
swap(p[h[ii].tp].th,p[h[i].tp].th);
i = ii;
ii = i << 1;
}
else return;
}
}
void slb(int i) //删链表
{
p[p[i].prv].nxt=p[i].nxt;
p[p[i].nxt].prv=p[i].prv;
}
void sd(int i) //删堆
{
if(i== --tot+1) return;
swap(h[i],h[tot+1]);
swap(p[h[i].tp].th,p[h[tot+1].tp].th);
up(i);
down(i);
}
int main() {
int w,n,k,pre;
cin>>n>>k>>pre;
for(int i=1;i<n;i++)
{
cin>>w;
p[i].d=w-pre;
p[i].prv=i-1;
p[i].nxt=i+1;
p[i].th=++tot;
pre=w;
h[tot].D=p[i].d;
h[tot].tp=i;
up(tot);
}
int ans=0;
for(int i=1;i<=k;i++) //核心代码,详见解析
{
ans+=h[1].D;
if(!p[h[1].tp].prv||p[h[1].tp].nxt==n) //如果在最左或者最右边的情况
{
if(!p[h[1].tp].prv)
{
sd(p[p[h[1].tp].nxt].th);
slb(p[h[1].tp].nxt);
}
else
{
sd(p[p[h[1].tp].prv].th);
slb(p[h[1].tp].prv);
}
slb(h[1].tp);
sd(1);
}
else //else
{
int tp0 = h[1].tp;
h[1].D = p[p[h[1].tp].prv].d + p[p[h[1].tp].nxt].d - p[h[1].tp].d; //神奇操作,详见下文解释将该节点 左右节点权值之和,减去该节点权值,加入堆中
p[h[1].tp].d = h[1].D; //也加入链表中
down(1);
sd(p[p[tp0].prv].th);
sd(p[p[tp0].nxt].th);
slb(p[tp0].prv);
slb(p[tp0].nxt);
}
}
cout<<ans;
}```
思路很简单,有点贪心的思想,要选相邻两个为一组是显然的,对于权值最小的一组,要么选他,要么他左右两组都选,如果不是这样,显然要比这样差,这里不过多解释。
下面解释一下那个神奇操作,有点反悔的意思,将它左右结点权值减去自己权值存入,将它看做一组,也就是2-1=1,在选k组时,不会影响到所选的数量。