这里写目录标题
G - Gathering in Yorknew
题意:
有一个无限长的 x 轴,给你 n 个点,每个点范围 [-1e9 , 1e9],所有点的目的地都是 0 点,移速每秒 1 格,你可以放置一个传送门,一进入传送门就直接被传送到 0 点,现在问你这 n 个点到 0 点的最小时间和(每个点到目的地的时间相加)是多少?
(一)首先,根据题意很容易就联想到当这些点有正有负时,只能把传送门放在 x 轴正半轴或者负半轴,在正半轴放传送门,负半轴的点无法使用,反之同理,所以处理的时候把两个半轴的情况都算一遍,然后取min。
(二)假设 n 个数全是正数,每个点到达 0 点的最小时间都是答案的一份子,看做这个点的贡献。对 n 个点讨论,假设传送门就建在当前点上,那么能否在 O(logn) 的时间内算出所有点的贡献之和?
答案当然是可以,假设这 n 个点都存到数组 a 里面,a 的前缀和数组是 s,下标 1 ~ n,首先对这 n 个点排序,然后循环,假设当前点下标为 k,那么以 a[k] 点为传送门的每个点的贡献和可以分成两部分—— k 左边所有数的贡献之和 and k右边所有数的贡献之和,我起名为 left 和 right。
right 很好想,就是区间[k + 1 , n] 每个点到 a[k] 的距离之和,利用前缀和推个公式就行
right = s[n] - s[k] - (n - k) * a[k]
left 也不难,需要考虑的是当有些点到 0 点的距离<到传送门的距离,那么他会优先选择去 0 点而不是去传送门。因为是有序排列,所以我们需要找到一个边界,这条边界的左边全是去 0 点的,这条边界的右边全是去传送门的,很容易看出来,这条边界是 a[k] / 2(上取整),然后二分去找就可以了,用二分找到第一个大于等于边界的下标,设下标为 t,
left = s[t - 1] + (k - t) * a[k] - (s[k - 1] - s[t - 1])
PS:这个二分有两种方法,一种很就是上面这样很简单,一种比较麻烦的,一开始脑子抽抽了,简单的没想到,写成难的了。
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
LL a[N];
LL b[N];
LL sa[N];
LL sb[N];
int cnt1 = 0, cnt2 = 0;
int main()
{
int n;
cin >> n;
for(int i = 1; i <= n; i ++ )
{
LL x;
cin >> x;
if(x < 0) a[ ++ cnt1] = -x;
else if(x > 0) b[ ++ cnt2] = x;
}
sort(a + 1, a + 1 + cnt1);
sort(b + 1, b + 1 + cnt2);
for(int i = 1; i <= cnt1; i ++ ) sa[i] = sa[i - 1] + a[i];
for(int i = 1; i <= cnt2; i ++ ) sb[i] = sb[i - 1] + b[i];
LL lmn = 1e18;
for(int i = 1; i <= cnt1; i ++ )
{
LL right = sa[cnt1] - sa[i] - (cnt1 - i) * a[i];
int l = 1, r = i;
int x = a[i] + 1 >> 1;
while(l < r)
{
int mid = l + r >> 1;
if(a[mid] >= x) r = mid; // 另一个版本: if(a[mid] >= a[i] - a[mid]) r = mid;
else l = mid + 1;
}
LL left = sa[r - 1] + (i - r) * a[i] - (sa[i - 1] - sa[r - 1]);
lmn = min(lmn, right + left);
}
LL rmn = 1e18;
for(int i = 1; i <= cnt2; i ++ )
{
LL right = sb[cnt2] - sb[i] - (cnt2 - i) * b[i];
int l = 1, r = i;
int x = b[i] + 1 >> 1;
while(l < r)
{
int mid = l + r >> 1;
if(b[mid] >= x) r = mid;// 另一个版本: if(b[mid] >= b[i] - b[mid]) r = mid;
else l = mid + 1;
}
LL left = sb[r - 1] + (i - r) * b[i] - (sb[i - 1] - sb[r - 1]);
rmn = min(rmn, right + left);
}
cout << min(rmn + sa[cnt1], lmn + sb[cnt2]) << endl;
}