luogu P3620

12 篇文章 0 订阅
2 篇文章 0 订阅

题意

一条数轴上有n个点,需要你给这n个点配对,每个点至多只能属于一个对,一共需要配k对,使得每对点的距离相加之和最小,保证有解

数据范围

1 ≤ n , k ≤ 1 e 5 , 0 ≤ 点 的 坐 标 ≤ 1 e 6 1\leq n,k\leq1e5,0\leq 点的坐标 \leq 1e6 1n,k1e5,01e6

解法

首先题目条件得出 k ≤ n / 2 k \leq n/2 kn/2,所以每对只会选择相邻两点,然后就有了一个初步的贪心:用小根堆维护相邻两点的距离,然后每次选堆顶。但是这显然是不对的:
例:n=5,k=2 1 3 4 6 20,然后就变成先选了[3,4],然后只能选[6,20],但是显然[1,3],[4,6]更优。所以我们需要加入反悔机制(这里和网络流中的反向边实际作用是一样的,实际上费用流也是一种贪心)。
具体来讲,当我们从堆中取出一个位置的值以后,将这个位置的值变为它现在左右位置(因为我们一直在不断取出位置,一个位置左右两端的位置是会改变的)的值减去自身的值。这样的正确性证明和网络流一样。(这其实就是模拟费用流的思路了)。
代码实现的时候注意边界问题,和判断取出的位置是否有效时的处理(不能粗暴的continue,因为这里循环的实现次数是有界的,不是q.empty())。

#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+5;
const int inf=0x3f3f3f3f;
inline int read(){
	char c=getchar();int t=0,f=1;
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){t=(t<<3)+(t<<1)+(c^48);c=getchar();}
	return t*f;
}
int n,k,last;
struct node{
	int val,l,r;
}a[maxn];
struct alfa{
	int val,id;
};
bool operator <(alfa a,alfa b){
	return a.val>b.val;
}
priority_queue<alfa> q;
int vis[maxn];
void calc(int x){
	a[x].l=a[a[x].l].l;
	a[x].r=a[a[x].r].r;
	a[a[x].l].r=x;
	a[a[x].r].l=x;
}
int main(){
	n=read(),k=read();last=read();
	for(int i=1;i<n;i++){
		int tmp=read();
		a[i].val=tmp-last;a[i].l=i-1;a[i].r=i+1;last=tmp;
		q.push((alfa){a[i].val,i});
	}
	a[0].val=a[n].val=inf;
	long long ans=0;
	while(k--){
		while(vis[q.top().id])q.pop();
		alfa u=q.top();q.pop();
		ans=ans+u.val;
		vis[a[u.id].l]=vis[a[u.id].r]=1;
		a[u.id].val=a[a[u.id].l].val+a[a[u.id].r].val-a[u.id].val;
		q.push((alfa){a[u.id].val,u.id});
		calc(u.id);
	}
	printf("%lld\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值