SDUT 2021 Autumn Team Contest 27th

这里写目录标题

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值