转化题意。把相邻两个办公楼之间的距离抽象为一个点的点权,问题就变为从 N − 1 N-1 N−1 个点中选择 K K K 个点,且这 K K K 个点不相邻,可以得到的最小点权和。
一种显然的贪心思路是简单地把所有点压入优先队列中,每次取出未被标记的点权最小的点累加到答案中,标记与它相邻的两个点。重复该过程 K K K 次。显然这是错误的,样例就可以 Hack。因为有可能标记的相邻点点权和比当前点和下一步匹配的远端点点权和更小。
引入一种类似于网络流中“退流”的思想,我们在取走一个最小点权点 a i a_i ai 的同时,再向优先队列中压入一个编号为 i i i,点权为 a i − 1 + a i + 1 − a i a_{i-1}+a_{i+1}-a_i ai−1+ai+1−ai 的点。如果该点能再次被选择,就相当于取与 a i a_i ai 相邻的点更优而不选 a i a_i ai 这个点, a i a_i ai 的点权被抵消了。
注意数组大小,数据范围要开 long long
。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
struct node
{
int id,v;
bool friend operator < (node a,node b){return a.v>b.v;}
};
priority_queue<node> q;
const int maxn=1e5+5;
struct link{int l,r,v;}a[maxn];
bool vis[maxn];
signed main()
{
int N,K;cin>>N>>K;
int tmp;cin>>tmp;
for(int i=1;i<N;i++)
{
int s;cin>>s;
a[i].v=s-tmp,tmp=s,a[i].l=i-1,a[i].r=i+1,q.push({i,a[i].v});
}
a[0].v=a[N].v=INT_MAX;
ll ans=0;
while(K--)
{
while(vis[q.top().id]) q.pop();
int x=q.top().id;q.pop();
vis[a[x].l]=vis[a[x].r]=1,ans+=a[x].v,a[x].v=a[a[x].l].v+a[a[x].r].v-a[x].v,q.push({x,a[x].v});
a[x].l=a[a[x].l].l,a[x].r=a[a[x].r].r,a[a[x].r].l=x,a[a[x].l].r=x;
}
cout<<ans;
return 0;
}