题目描述
难度分:1704
输入n(2≤n≤3×105),然后输入n行,每行两个数,表示一个闭区间的左右端点L[i]和 R[i],数值范围在[1,107]内。
你需要从每个闭区间内,各选择一个整数。这n个数构成了数组x。
设s为所有|x[j]−x[i]|的和,其中i<j。
相当于把x中的所有数放在一条数轴上,计算所有点对的距离之和。
输出s的最小值。
输入样例1
3
1 3
2 4
5 6
输出样例1
4
输入样例2
3
1 1
1 1
1 1
输出样例2
0
输入样例3
6
1 5
2 4
1 1
4 4
3 6
3 3
输出样例3
15
算法
贪心
由于是绝对值,我们将x有序与否并不影响目标函数s=Σn−1i=1Σnj=i+1|x[j]−x[i]|的值。但是如果x有序,就可以去掉绝对值,最终将目标函数化为
s=(n−1)×(x[n]−x[1])+(n−3)×(x[n−1]−x[2])+…=Σn2i=1(n−2i+1)×(x[n−i+1]−x[i])
这是因为x[n]要减去x[n−1]、x[n−2]、……、x[1],s中一共有n−1个x[n];而x[n−1]要减去x[n−2]、x[n−3]、……、x[1],一共n−2个x[n−1],但因为x[n−1]要被x[n]减一次,所以减少一个x[n−1],s中一共有n−3个x[n−1]。x[1]被比它大的n−1个数减了,所以一共有n−1个−x[1];x[2]被比它大的n−2个数减了,但由于x[2]还会减一次x[1],所以又加回来一个x[2],只有n−3个−x[2]。此时我们可以将x[1]和x[n]配对,x[2]和x[n−1]配对,分别提取公因式n−1和n−3,接下来依此类推分析(x[n−2],x[3]),(x[n−3],x[4]),……即可。
因此我们在x有序的情况下考虑s的最小值,显然x[1]和x[n]越近肯定s就越小,x[1]最大可以取到最小的R值,否则就没有办法从R最小的那个区间里选数;同理,x[n]最小可以取到最大的L值,否则也没有办法从L最大的那个区间里选数。
- 当x[1]<x[n]时,可以计算它们对答案的贡献,然后去掉这两个点n=n−2,继续考虑规模更小的子问题。
- 当x[1]≥x[n]时,所有区间存在交集,我们可以在每个区间中选择同一个数,使得它们对答案的贡献为0,因此碰到这种情况直接跳过就行了。
实现时可以把左端点和右端点分别排序(左端点降序排列,右端点升序排列,x[n]从左端点里面选,x[1]从右端点里面选),然后循环扫一遍即可求得答案。每次循环相当于计算s中一项的贡献,即每次选x[n]−x[1]最小的计算贡献(更新n=n−2之后,x[2]和x[n−1]就变成了新的x[1]和x[n])。
复杂度分析
时间复杂度
瓶颈主要在于两次排序,剩下扫描求最终结果的时间复杂度是线性的,因此算法整体的时间复杂度是O(nlog2n)。
空间复杂度
需要存储输入的(L[i],R[i]),然后对它们分别排序,因此额外空间复杂度为O(n)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 300010;
int l[N], r[N], n;
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d%d", &l[i], &r[i]);
}
sort(l + 1, l + n + 1);
reverse(l + 1, l + n + 1);
sort(r + 1, r + n + 1);
LL ans = 0;
for(int i = 1; i <= n && l[i] > r[i]; i++) {
ans += (LL)(n - 2*i + 1)*(l[i] - r[i]);
}
printf("%lld\n", ans);
return 0;
}