数据备份
在坐标轴上有n个点,第i个点的坐标为\(x_i\),现在请选出k对点(一个点不能同时在两对以上的点中),使每一对点的距离之和最小,求出这个最小值,\(2≤n≤100000,1≤k≤n/2\)。
解
首先容易知道的结论是一对点必然选择的是相邻的,否则在最优解,将两个点变为相邻,结果会更加优秀。
将点的坐标从小到大排序(原题给出的就是有序的),处理出两个点间的距离,构成一个长度为n-1的数列\(\{a_i\}\),以后所说的n都为\(\{a_i\}\)的长度,现在问题转化成从这个数列中选出k个数,不相邻的和的最小值。
数列已经是有序的,于是我们不应该排序,最多是构造新序列,排好序,然后再构造一个序列对应其在原序列的位置,然后从小往大选,但是问题在于这个数不一定选,往后看就明白了
朴素的最优性贪心的思想,告诉我们要从最小的数\(a_i\)开始,但是它一定选吗?它选了之后,最终导致与之相邻的两个数不能选择,容易知道,决策要么是选这个数\(a_i\),要么是选\(a_{i-1},a_{i+1}\),因为如果\(a_{i-1},a_{i+1}\)只选一个,改成\(a_i\)会更加优秀,因此一个数字延伸多种决策,而这个数字又是最优秀的数字,考虑合并性贪心思想。
多种决策不妨将\(a_{i-1},a_i,a_{i+1}\)合并成一个数字,对应的值\(a_{i-1}+a_{i+1}-a_i\),此时答案累加\(a_i\),如果接下来选了\(a_{i-1}+a_{i+1}-a_i\),表示选了\(a_{i-1}+a_{i+1}\),否则表示选了\(a_i\),正好前者对应两次选择,后者对应一次选择,于是我们成功地将多种决策合并成一种决策,三个数字合并成一个数字。
因此按这样选择的k个数字,最好累加的答案,就是我们需要的,注意的是如果一个点没有两个点可以和它相邻,还是要删去所有与它相邻的点,表示这个点以后不会决策。
因此对于以上思路,我们只要用小根堆维护取出最小值,用链表维护数字的合并,让小根堆顺带记录其在链表上的位置,就可以支持快速合并和查询,最终时间复杂度\(O(klog(n))\),这是一道很棒的思维题,但是我想不到。
参考代码:
#include <iostream>
#include <cstdio>
#define il inline
#define ri register
#define Size 200100
using namespace std;
template<class free>
struct small{
free a[Size];int n;
il void push(free x){
a[++n]=x;ri int p(n);
while(p>1)
if(a[p]<a[p>>1])
swap(a[p],a[p>>1]),
p>>=1;else break;
}
il void pop(){
a[1]=a[n--];ri int p(1),s(2);
while(s<=n){
if(s<n&&a[s+1]<a[s])++s;
if(a[s]<a[p])
swap(a[s],a[p]),p=s,s=p<<1;
else break;
}
}
};
template<class free>
struct list{
struct iter{
iter *pre,*next;free v;
}*head,*tail,*lt;
il void initialize(){
head=new iter(),tail=new iter();
head->next=tail,tail->pre=head;
}
il void insert(iter*p,free v){
lt=new iter{p->pre,p,v};
p->pre->next=lt,p->pre=lt;
}
il void erase(iter*p){
p->pre->next=p->next;
p->next->pre=p->pre;
p->v=-1;
}
};
struct er{
int d;list<int>::iter *p;
il bool operator<(const er&x)const{
return d<x.d;
}
};
int s[Size];
list<int>L;small<er>S;
list<int>::iter *m,*l,*r;
il void read(int&);
int main(){
int n,k,ans(0);
read(n),read(k),L.initialize();
for(int i(1);i<=n;++i)read(s[i]);
--n;for(int i(1);i<=n;++i)
s[i]=s[i+1]-s[i],L.insert(L.tail,s[i]),
S.push((er){s[i],L.tail->pre});
for(int i(1);i<=k;++i){
while(m=S.a[1].p,m->v==-1)S.pop();
l=m->pre,r=m->next,ans+=m->v;
if(l==L.head&&r==L.tail)break;
if(l==L.head)L.erase(r),L.erase(m);
else if(r==L.tail)L.erase(l),L.erase(m);
else{
L.insert(r,l->v+r->v-m->v);
L.erase(m),m=r->pre,S.push({m->v,m});
L.erase(l),L.erase(r);
}S.pop();
}printf("%d",ans);
return 0;
}
il void read(int &x){
x^=x;ri char c;while(c=getchar(),c<'0'||c>'9');
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}