反悔贪心 ——— 【AcWing.147】数据备份

题目

反悔贪心 ——— 【AcWing.147】数据备份_第1张图片
反悔贪心 ——— 【AcWing.147】数据备份_第2张图片
反悔贪心 ——— 【AcWing.147】数据备份_第3张图片

输入样例:

5 2 
1
3
4
6
12

输出样例:

4

题目分析

初步思路

这道题一看,第一反应就是贪心,因为要总距离最小,所以肯定要每一段都尽量的小
换句话说,因为 k < n 2 k < \frac{n}{2} k<2n 所以每一个最优解中的线段的两个端点一定是相邻的城市

A:

每次选其中最小的一段会不会影响其余的段(因为一个楼只能做为端点一次),使得全局答案不能为最优呢?

Q:

肯定的,举个例子
反悔贪心 ——— 【AcWing.147】数据备份_第4张图片
第一次最小肯定选 S 2 S_2 S2 ,然后第二次就只能选 S 1 + S 2 + S 3 S_1+S_2+S_3 S1+S2+S3 所以总计 S 2 + ( S 1 + S 2 + S 3 ) S_2 + (S_1 + S_2+S_3) S2+(S1+S2+S3)
但是换一种方法呢?
第一次选 S 1 S1 S1 ,然后第二次就可以选 S 3 S_3 S3,总计 S 1 + S 3 S_1 + S_3 S1+S3 明显更优。


那么问题就来了,这道题是不是不能贪心的求解了呢?

正确思路

肯定不是的,古人云:知错能改,善莫大焉,那么如果我们再选择的时候已经知道自己错了,我们用已知的更好的答案修改以前的答案,最后的得到的就是更好的答案

先分析一下,还是刚才那幅图
反悔贪心 ——— 【AcWing.147】数据备份_第5张图片
如果我们选择 S 2 S_2 S2 那么就不能选 S 1 , S 3 S_1,S_3 S1,S3
如果我们选择 S 1 , S 3 S_1,S_3 S1,S3 那么就不能选 S 2 S_2 S2
所以综上我们有两种方法,全局的所有方法中取最好的 K K K中即可


好了,改错的思路有了,方法也很重要
首先因为我们知道的是每个点的坐标,所以先要处理出每条选段的长度
设 S e g [ i ] = P [ i + 1 ] − P [ i ] ( 一种 P 是输入的楼房坐标 ) 设 Seg[i] = P[i + 1] - P[i] (一种P是输入的楼房坐标) Seg[i]=P[i+1]P[i](一种P是输入的楼房坐标)
然后我们要给程序反悔的机会
如果先选择了 S e g [ i ] Seg[i] Seg[i] 然后发现不好,因该选 S e g [ i − 1 ] + S e g [ i + 1 ] Seg[i - 1] + Seg[i + 1] Seg[i1]+Seg[i+1]则么办呢?
显然,减去 S e g [ i ] Seg[i] Seg[i] 再加上 S e g [ i − 1 ] + S e g [ i + 1 ] Seg[i - 1] + Seg[i + 1] Seg[i1]+Seg[i+1]即可

所以我们再选择 S e g [ i ] Seg[i] Seg[i]的同时,把 S e g [ i − 1 ] + S e g [ i + 1 ] − S e g [ i ] Seg[i - 1] + Seg[i + 1] - Seg[i] Seg[i1]+Seg[i+1]Seg[i]也加入备选的答案中不就可以了吗?这个显然可以用堆维护


那么最终的思路也就有了:
首先处理 S e g [ i ] = P [ i + 1 ] − P [ i ] Seg[i] = P[i + 1] - P[i] Seg[i]=P[i+1]P[i]
然后依次把 S e g [ i ] Seg[i] Seg[i] 放入答案
每次选取备选答案中最小的计入答案
最后删除 S e g [ i − 1 ] , S e g [ i ] , S e g [ i + 1 ] Seg[i - 1],Seg[i],Seg[i + 1] Seg[i1],Seg[i],Seg[i+1]三个点(因为暂时都不能选了)
S e g [ i − 1 ] + S e g [ i + 1 ] − S e g [ i ] Seg[i - 1] + Seg[i + 1] - Seg[i] Seg[i1]+Seg[i+1]Seg[i]插入 i i i号位置(加入备选答案)

因为每次 i − 1 i - 1 i1 i + 1 i + 1 i+1不一定是原数组中相邻的两个,所以我们可以用链表维护,然后开一个 d e l del del 数组标记被删除就好了

代码实现

#include 
using namespace std;
const int N = 1e5, Inf = 1e9;
int n, k, pre[N + 5], nxt[N + 5];
long long ans,pos[N + 5], seg[N + 5];
bool del[N + 5];
struct cmp {
	bool operator()(int x,int y) {
		return seg[x] > seg[y];
	}
}; // 一个很牛逼的技巧
priority_queue<int,vector<int>,cmp> q;
int main() {
	scanf("%d%d", &n, &k);
	for (int i = 1; i <= n; i++)
		scanf("%d", &pos[i]);
	for (int i = 1; i < n; i++)
		seg[i] = pos[i + 1] - pos[i];
	n --;
	seg[0] = Inf;
	for (int i = 1; i <= n; i++) {
		q.push(i);
		pre[i] = i - 1;
		nxt[i] = i + 1 > n ? 0 : i + 1;
	}
	while(k --) {
		int tp = q.top(); q.pop();
		while(del[tp]) {
			tp = q.top();
			q.pop();
		}
		ans += seg[tp];
		del[pre[tp]] = 1;
		del[nxt[tp]] = 1;
		seg[tp] = seg[pre[tp]] + seg[nxt[tp]] - seg[tp];
		q.push(tp);
		pre[tp] = pre[pre[tp]];
		nxt[tp] = nxt[nxt[tp]];
		nxt[pre[tp]] = tp;
		pre[nxt[tp]] = tp;
	}
	printf("%lld",ans);
	return 0;
}

你可能感兴趣的:(信息学竞赛,算法)