【算法分析】
n,k都有10^5,所以考虑贪心算法
最基本的贪心就是任意一对数必须是相邻的,这是显然的。
考虑到一重要结论:
假设现在有三条相邻的线段a1,a2,a3;a1>a2,a3>a2,如果a2小于等于最优解集中的最大元素,并且最优解集中不存在a2,则必同时存在a1,a3。
证明:如果最优解中只有a1或只有a3,那我可以把它换成a2,这组解仍然满足要求,但总距离更小。如果最优解中不存在a1或a3,那我可以用a2换掉解集中的最大元素。由于上述两种情况都不可能,所以题设成立。
但要注意,当线段在两端时就不满足了,因此,需要在两端加两个距离无穷大的哨兵。
利用这一点我们就可以构造一个增量算法:
每次选取一个最小的线段,然后把这条线段左右的两条线段合并在一起,新线段的权值为左右之和减去中间。
每一次操作之后,都会使解集中增加一条线段,而总费用增长了一个最小值。
【教训】
写del()函数的时候忘了将swap(pos[h[x]],pos[h[r]])写进去了。wa了很久
以后还是写一个change函数算了。
【代码】
#include <iostream>
#include <cstring>
#include <string>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N=100005,INF=1<<30;
int a[N],b[N],pos[N],L[N],R[N],h[N];
int r;
void up(int x)
{
int i=x,j;
while (i>1)
{
j=i>>1;
if (b[h[j]]>b[h[i]])
{
swap(h[i],h[j]);
swap(pos[h[i]],pos[h[j]]);
i=j;
}
else break;
}
}
void down(int x)
{
int i=x,j;
while (i*2<=r)
{
j=i*2;
if (j+1<=r && b[h[j]]>b[h[j+1]]) j++;
if (b[h[i]]>b[h[j]])
{
swap(h[i],h[j]);
swap(pos[h[i]],pos[h[j]]);
i=j;
}
else break;
}
}
void ins(int i)
{
h[++r]=i;
pos[i]=r;
up(r);
}
void del(int x)
{
swap(h[x],h[r]);
swap(pos[h[x]],pos[h[r]]);
r--;
up(x);
down(x);
}
int main()
{
int i,k,n,ans,l,r,mid;
freopen("14.in","r",stdin);
scanf("%d%d",&n,&k);
for (i=1;i<=n;i++)
scanf("%d",&a[i]);
for (i=2;i<=n;i++)
{
b[i]=a[i]-a[i-1];
ins(i);
}
b[1]=b[n+1]=INF;
ins(1);ins(n+1);
for (i=1;i<=n+1;i++)
L[i]=i-1,R[i]=i+1;
ans=0;
while (k--)
{
mid=h[1];
ans+=b[mid];
l=L[mid];
r=R[mid];
L[R[r]]=l;
R[l]=R[r];
del(pos[mid]);
del(pos[r]);
b[l]=b[l]+b[r]-b[mid];
up(pos[l]);
down(pos[l]);
}
printf("%d\n",ans);
}