1. 程式人生 > 其它 >P3620 [APIO/CTSC 2007] 資料備份 題解

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\n",Ans);
	}
	return 0;
}