题意:给定n(2 <= n <= 100000)个点,从中选k(1 <= k <= n/2)对点,使得所有点对距离之和最小,每个数s都是0 <= s <= 1000000000,并且输入从小到大排序。
时间限制:10000ms
通过时间:488ms
分析:
我看到这题第一反应是dp,设f(i, j)表示前i个点,选j对,距离之和的最小值。
不难列出状态转移方程:f(i, j) = min(f(i-1, j), f(i-2, j-1) + a[i] - a[i-1]).
这样时空复杂度都是O(nk),空间开不下,观察到j只依赖j-1,所以可以滚动数组,提交后不幸发现TLE.
看来CTSC的题不是那么简单的,冥想半天不得其解,只好去网上寻找灵丹妙药。
进行一番搜索后,得出这道题做法如下:
首先由于每次选的点对必定是相邻的,所以我们可以求出n-1个相邻的差值, 原题变为,从n-1个数选出k个不相邻的数,使得这些数的和最小。
zrt把这道题归到了STL,可以用小根堆(优先队列)来做。
1.把所有差值放入堆中;
2.取出最小的值x;
3.把这个点改成它左边的点 + 右边的点 - x,插入堆中;
4.把x左右两边的点都删去;
5.返回到2,重复k次这个过程。
选左 + 右 - x表示不选x而选了x的左和右。
那,为什么这个是对的呢?
如果最终选了x,那么自然就是最优的,如果最终没有选x,那么一定会选x当前的两边,为什么呢?因为如果选了非x的两边,那么一定可以把另一边选成x,比当前解不会差,得证。
考虑一下实现方法,首先不仅要知道最小的值是多少,还要知道是第几个点,所以这里使用了一下pair,默认是按照第一维排序,于是第一维放差值,第二维放序号。
为了知道它左右的点,所以我要开两个数组pre和nxt分别表示前缀和后缀,初始化pre[2] = nxt[n] = 0,采用链表的思想进行更新。
删去操作并不一定要把它真的从队列里pop出来,我可以把它的值修改,然后第二步之前判断如果队列里的值和它实际的值不一样,说明已经选过了,pop()掉即可。
最后考虑一下它的边界,当我选第一个点或最后一个点的时候,显而易见可以证明选第二个点或第n-1个点绝对没有当前决策好,所以直接去除第一个点或第n-1个点。
既然是CTSC的题,可能会有点小坑,数的范围是0~10^9,所以inf要开大一点,我就是被这个坑到死...
dp代码(TLE):
#include <iostream> #include <cstdio> using namespace std; int n, k, f, a[100005], d[100005][2]; int main() { scanf("%d%d", &n, &k); for(int i = 1; i <= n; i++) scanf("%d", &a[i]); for(int j = 1; j <= k; j++) { f ^= 1; for(int i = j*2; i <= n; i++) { if(i > j * 2) d[i][f] = d[i-1][f]; else d[i][f] = 100000000; d[i][f] = min(d[i][f], d[i-2][f^1] + a[i] - a[i-1]); } } printf("%d", d[n][f]); return 0; }
正解:代码很短,但是蕴含的东西很多。
#include <cstdio> #include <queue> using namespace std; typedef pair<int, int> pr; const int inf = 0x3f3f3f3f; int n, k, x, y, ans, len[100005], pre[100005], nxt[100005]; priority_queue<pr, vector<pr>, greater<pr> > q; int main() { scanf("%d%d", &n, &k); for(int i = 1; i <= n; i++) { scanf("%d", &x); len[i] = x-y, y = x; if(i != 1) q.push(make_pair(len[i], i)); pre[i] = i-1, nxt[i] = i+1; } pre[2] = 0, nxt[n] = 0; while(k--) { while(q.top().first != len[q.top().second]) q.pop(); int u = q.top().second, l = pre[u], r = nxt[u]; q.pop(); ans += len[u]; len[u] = l && r ? len[l] + len[r] - len[u] : inf; pre[nxt[u] = nxt[r]] = u; nxt[pre[u] = pre[l]] = u; len[l] = len[r] = inf; q.push(make_pair(len[u], u)); } printf("%d\n", ans); return 0; }