P3620 [APIO/CTSC 2007] 数据备份 题解

题目大意

数轴上有(N)个点,可以用(K)条线连接(2K)条边,使得连的边的总长度最小

P3620 [APIO/CTSC 2007] 数据备份

问题求解

显然,连接相邻的两个是最好的,所以可以建立(N-1)个差分,相邻的差分不能选,题目也就被转化成了,(N-1)个数里面选(K)个数,相邻的数不能选,使得数的总和最大

我们很容易可以想到一个贪心,用一个堆每次取小的,讲左边和右边标记为不可选点,但是也很显然是错误的:(2 1 2 6)就可以(hack)

所以考虑怎么返回,我们在选一个点时,要在堆中加入一个数,数的大小为左边数的大小(+)右边数的大小(-)自身的大小,连续取这个数两次就相当于这个数边上的两个数了,贪心求解即可

代码实现

#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
const int maxn=100005,INF=0x3f3f3f3f;
int N,K,Ans,T;
int vis[maxn];
struct place{
	int val,l,r;
}p[maxn];
struct AS{
	int val,id;
	bool operator <(const AS B)const {return val>B.val;}
};
priority_queue<AS> q;
inline int read(){
	int ret=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
	while(ch<='9'&&ch>='0')ret=ret*10+ch-'0',ch=getchar();
	return ret*f;
}
void del(int x){
	p[x].l=p[p[x].l].l;p[x].r=p[p[x].r].r;
	p[p[x].l].r=x;p[p[x].r].l=x;
}
int main(){
	freopen("P3620.in","r",stdin);
	freopen("P3620.out","w",stdout);
	T=read();
	while(T--){
		memset(p,0,sizeof p);
		while(!q.empty())q.pop();
		N=read();K=read();int lst=read();
		for(int i=1;i<N;i++){
			int now=read();
			p[i].val=now-lst;
			lst=now;p[i].l=i-1;p[i].r=i+1;
			q.push((AS){p[i].val,i});
		}
		p[0].val=p[N].val=INF;
		for(int i=1;i<=K;i++){
			while(vis[q.top().id])
			 q.pop();
			AS now=q.top();q.pop();
			Ans+=now.val;vis[p[now.id].l]=vis[p[now.id].r]=1;
			p[now.id].val=p[p[now.id].l].val+p[p[now.id].r].val-p[now.id].val;
			q.push((AS){p[now.id].val,now.id});
			del(now.id);
		}
		printf("%d
",Ans);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/martian148/p/15185665.html