【题解】LuoGu3620: [APIO/CTSC 2007]数据备份

原题传送门
首先转化题意,可以用贪心策略证明肯定是相邻的两个点建立电缆
然后把问题转化成 a i = s i + 1 − s i a_i=s_{i+1}-s_i ai=si+1si, a 1 , a 2 , . . . , a n − 1 a_1,a_2,...,a_{n-1} a1,a2,...,an1中取 k k k个不相邻的数使得和最小

首先非常明显的 O ( n k ) naiveDP O(nk)\text{naiveDP} O(nk)naiveDP,可以拿到60分的好成绩
然后考虑优化,dp上状态已经 O ( n k ) O(nk) O(nk),到顶了,除非设计更妙的 O ( n ) O(n) O(n)状态,但是数据范围告诉我们,需要一个 O ( n l o g n ) O(nlogn) O(nlogn)的做法

于是想到反悔贪心
从头思考,一个非常 n a i v e naive naive的贪心,维护优先队列,每次取最小的,并且把左右两个打上标记,因为相邻的两个不能都选,这样取 k k k次即可

h a c k hack hack显然,但是我们可以增添一个反悔机制,每次从优先队列取出一个未标记的值时,首先这个值肯定合理并且当前最优,那么就加入答案,但是这个值不一定全局最优,所以以后可能反悔,那么怎么反悔呢,当然就是不选这个值了,改选这个值左右两边的两个值

具体流程就是对于一个取出的值 a j a_j aj,统计答案,并且将值 a j − 1 + a j + 1 − a j a_{j-1}+a_{j+1}-a_j aj1+aj+1aj加入优先队列,这样依然是做 k k k次,因为每做一次就会多一个数

然后不要忘了打标记,这里我们需要用双向队列维护一下左右两边的值

Code:

#include 
#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;
}

你可能感兴趣的:(题解,LuoGu,优先队列(堆))