题目链接:BZOJ – 1150
题目分析
可以看出,我们选的 k 条边一定是相邻两点之间的线段。我们可以将每条边看成一个点,那么我们就是要在 n-1 个点中选出互不相邻的 k 个,使它们的和最小。
我们使用一种神奇的贪心,开始的时候将所有的点权加入堆中,然后取 k 次,每次取权值最小的点,然后将这个点的点权加入答案中,我们取了这个点之后就不能取与它相邻的两个点了,所以我们要将这个点和相邻两点的权值都从堆中删除。但是我们最终的答案并不一定要取这个点,有可能我们会不取这个点,而是取与它相邻的两个点。所以我们在删除了这个点和与它相邻的两个点之后,要在堆里加入一个新的点:权值是与这个点相邻的两个点的权值和-这个点的权值,如果之后取到这个新的点就相当于撤销取这个点,而是取与它相邻的两个点,这样取到的点数还是加一的。
然后这样处理之后我们就认为这个新建的点取代了之前的3个点的位置,然后与这3个点两边的点相邻,需要用链表维护。
还有特殊的情况,对于两端的点,与它们相邻的点只有1个,我们要在第一个点之前和最后一个点之后分别加上一个权值为 INF 的点,这样进行处理之后新建的点的权值也一定非常大,之后就不会被取到。
代码
#include <iostream> #include <cstdlib> #include <cstring> #include <cstdio> #include <cmath> #include <algorithm> #include <set> using namespace std; const int MaxN = 100000 + 5, INF = 999999999; int n, k, Ans; int A[MaxN], d[MaxN], Next[MaxN], Prev[MaxN]; struct ES { int x, y; ES() {} ES(int a, int b) {x = a; y = b;} bool operator < (const ES &e) const { if (y == e.y) return x < e.x; return y < e.y; } }; set<ES> S; set<ES> :: iterator It; int main() { scanf("%d%d", &n, &k); for (int i = 1; i <= n; ++i) { scanf("%d", &d[i]); A[i] = d[i] - d[i - 1]; } for (int i = 2; i <= n; ++i) { Next[i] = i + 1; Prev[i] = i - 1; S.insert(ES(i, A[i])); } A[1] = A[n + 1] = INF; int x; for (int i = 1; i <= k; ++i) { It = S.begin(); x = (*It).x; Ans += A[x]; S.erase(ES(x, A[x])); S.erase(ES(Prev[x], A[Prev[x]])); S.erase(ES(Next[x], A[Next[x]])); A[x] = A[Prev[x]] + A[Next[x]] - A[x]; S.insert(ES(x, A[x])); if (Prev[Prev[x]]) Next[Prev[Prev[x]]] = x; if (Next[Next[x]]) Prev[Next[Next[x]]] = x; Prev[x] = Prev[Prev[x]]; Next[x] = Next[Next[x]]; } printf("%d\n", Ans); return 0; }