1. 程式人生 > 其它 >【luogu P3620】資料備份(反悔貪心 / 撤回貪心)

【luogu P3620】資料備份(反悔貪心 / 撤回貪心)

給你一個數軸上面的一些點,你要選若干個點對,使得每個點至多在一個點對中,而且要你最小化每個點對之間距離的和。

資料備份

題目連結:luogu P3620

題目大意

給你一個數軸上面的一些點,你要選若干個點對,使得每個點至多在一個點對中,而且要你最小化每個點對之間距離的和。

思路

首先考慮普通的貪心。
就是每次選費用最小的那個,然後把它兩邊可以選的刪掉。

但是你可能選旁邊兩個而不選它更優,所以考慮一個撤回操作:
你選了之後刪掉兩個旁邊的和自己之後,加入旁邊兩個減去自己的值,那選了這個就抵消了這次選自己的,然後選了旁邊的。(總體來講也是多選了一個)

然後因為你旁邊可能早就被刪了,那你要找到的是旁邊第一個沒有被刪的,所以我們可以用雙向連結串列來維護。

程式碼

#include<queue>
#include<cstdio>
#define ll long long

using namespace std;

struct pl {
	int val, l, r;
}a[100001];
int n, k, x, lst;
bool cnot[100001];
ll ans;

struct node {
	int pl, val;
};

bool operator <(node x, node y) {
	return x.val > y.val;
}

priority_queue <node> q;

void Delete(int now) {//刪掉它兩邊的點
	a[a[a[now].l].l].r = now;
	a[a[a[now].r].r].l = now;
	a[now].l = a[a[now].l].l;
	a[now].r = a[a[now].r].r;
}

int main() {
	scanf("%d %d %d", &n, &k, &lst);
	for (int i = 1; i < n; i++) {
		scanf("%d", &x);
		
		a[i].l = i - 1;
		a[i].r = i + 1;
		a[i].val = x - lst;
		q.push((node){i, a[i].val});
		
		lst = x;
	}
	
	a[0].val = a[n].val = 1e9 + 1e7;//注意邊界是要儘可能不優
	for (int i = 1; i <= k; i++) {
		while (cnot[q.top().pl]) q.pop();
		
		node now = q.top();
		q.pop();
		ans += now.val;
		cnot[a[now.pl].l] = cnot[a[now.pl].r] = 1;
		a[now.pl].val = a[a[now.pl].l].val + a[a[now.pl].r].val - a[now.pl].val;//反悔的花費
		q.push((node){now.pl, a[now.pl].val});
		Delete(now.pl);
	}
	printf("%lld", ans);
	
	return 0;
}