【題解】LuoGu3620: [APIO/CTSC 2007]數據備份

原題傳送門
首先轉化題意,可以用貪心策略證明肯定是相鄰的兩個點建立電纜
然後把問題轉化成ai=si+1sia_i=s_{i+1}-s_i,a1,a2,...,an1a_1,a_2,...,a_{n-1}中取kk個不相鄰的數使得和最小

首先非常明顯的O(nk)naiveDPO(nk)\text{naiveDP},可以拿到60分的好成績
然後考慮優化,dp上狀態已經O(nk)O(nk),到頂了,除非設計更妙的O(n)O(n)狀態,但是數據範圍告訴我們,需要一個O(nlogn)O(nlogn)的做法

於是想到反悔貪心
從頭思考,一個非常naivenaive的貪心,維護優先隊列,每次取最小的,並且把左右兩個打上標記,因爲相鄰的兩個不能都選,這樣取kk次即可

hackhack顯然,但是我們可以增添一個反悔機制,每次從優先隊列取出一個未標記的值時,首先這個值肯定合理並且當前最優,那麼就加入答案,但是這個值不一定全局最優,所以以後可能反悔,那麼怎麼反悔呢,當然就是不選這個值了,改選這個值左右兩邊的兩個值

具體流程就是對於一個取出的值aja_j,統計答案,並且將值aj1+aj+1aja_{j-1}+a_{j+1}-a_j加入優先隊列,這樣依然是做kk次,因爲每做一次就會多一個數

然後不要忘了打標記,這裏我們需要用雙向隊列維護一下左右兩邊的值

Code:

#include <bits/stdc++.h>
#define maxn 100010
using namespace std;
struct node{
	int num, val;
	bool operator < (const node &x) const{ return x.val < val; }
};
priority_queue <node> q;
struct data{
	int l, r, val;
}a[maxn];
int n, k, vis[maxn], ans;

inline int read(){
	int s = 0, w = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
	for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48);
	return s * w;
}

void del(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();
	int Last = read();
	for (int i = 1; i < n; ++i){
		int x = read();
		a[i].val = x - Last, Last = x, a[i].l = i - 1, a[i].r = i + 1;
		q.push((node){i, a[i].val});
	}
	a[0].val = a[n].val = 1e9;
	for (int i = 1; i <= k; ++i){
		while (vis[q.top().num]) q.pop();
		node tmp = q.top(); q.pop();
		ans += tmp.val;
		vis[a[tmp.num].l] = vis[a[tmp.num].r] = 1;
		a[tmp.num].val = a[a[tmp.num].l].val + a[a[tmp.num].r].val - a[tmp.num].val;
		q.push((node){tmp.num, a[tmp.num].val});
		del(tmp.num);
	}
	printf("%d\n", ans);
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章