牛客网暑期ACM多校训练营(第二场)G.transform (二分+思维)

题目链接

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

White Cloud placed n containers in sequence on a axes. The i-th container is located at x[i] and there are a[i] number of products in it.
White Rabbit wants to buy some products. The products which are required to be sold must be placed in the same container.
The cost of moving a product from container u to container v is 2*abs(x[u]-x[v]).
White Cloud wants to know the maximum number of products it can sell. The total cost can't exceed T.

输入描述:

The first line of input contains 2 integers n and T(n <= 500000,T <= 1000000000000000000)
In the next line there are n increasing numbers in range [0,1000000000] denoting x[1..n]
In the next line there are n numbers in range[0,10000] denoting a[1..n]

输出描述:

Print an integer denoting the answer.

示例1

输入

2 3
1 2
2 3

输出

4

题意:数轴上有n个集装箱,第i个集装箱位于坐标d[i],有a[i]件货物.现在要把集装箱进行一些移动,求所有货物移动花费不超过T的情况下,最多能把多少个集装箱移动到同一个位置?(在将得到的T/2之后,移动花费计算方式为:从u移动一个货物到v花费为|d[u]-d[v]| )

题解:首先对于给出的所有集装箱货物的数目我们可以知道题目输出的答案一定在[最小的集装箱货物,集装箱货物总和]这个区间内,并且题目给出的数据均在longlong范围内支持我们对数据进行二分答案判断该答案是否可以总花费T内达到.

        对于每次二分得到的答案,我们需要在O(n)的时间复杂度内判断出该答案是否能得到,那么我们需要先预处理出2个前缀和:

        Sum[maxn];    //sum[i]表示第1~i个集装箱所有货物数总和
             F[maxn];    //   f[i]   表示第1~i个集装箱的所有货物移到x=0处所需费用

        对于每次判断二分得到的答案(下面统称:需求货物量)是否正确,下面代码写的是一个OK()函数,该函数主要思想是通过维护一个区间,确保这个区间内货物的总和大于等于需求货物量,然后通过移动u指针找到该区间内的最优货物集中点,若是将货物移动到最优货物集中点使得该点货物量满足需求且所需的费用小于等于T,说明该点满足条件(即该需求货物量能达到) 

         至于为什么在函数内需要进行2个时间复杂度为O(n)的计算操作,第一个while()是从1开始维护区间的,且当区间[L,R]内的货物总和大于X的,我们优先取最左边的货物,且区间逐渐向右移动,直到得到满足条件的情况或者后面区间货物了总和不可能达到要求结束循环;第二个while()是从n开始维护区间的,且当区间[L,R]内的货物总和大于X的,我们优先取最右边的货物,且区间逐渐向左移动,

         :为什么都是优先取边界的(即优先放弃离最优货物集中点最远的点,那么这个点只能是L或者R),若是第一个while循环选择优先放弃R的不是最优,第二个while循环也能补上,只要保证我们能在有限时间内判断出该需求货物量能在花费T内达到就行~

         至于2个cnt函数是2个while内部的计算,即2种优先取法下,将[L,R]中货物集中到 i 处使之货物量达到需求货物量所需的费用.

        计算如下:(其中x为需求货物量)

                       费用=将区间L+1~i中所有货物移动到i处所需的费用 + 将区间i+1~R中所有的货物移动到i处所需的费用

                                                                                                         - 区间L+1~R比x多的货物数从R处移动到i处的费用)

         下面介绍下如何使用前缀和的方法O(1)计算出区间内货物移动所需的费用:(举CNT_1函数的例子)

         (如果看不懂我代码的CNT函数可以考虑看下面内容自己手推,代码是自己手推的=.=)

         F[i]-F[L] 可以通俗的表示为将L+1~i 中的所有货物移动到x=0处获得的费用(没错,就是获得)

         (sum[i]-sum[L])*d[i] 表示为将L+1~i处的货物全部从x=0处移动到d[i]所消耗的费用(这里才是消耗)

          那么 (sum[i]-sum[L])*d[i]   -   ( F[i]-F[L] )  就是将区间[L+1,R]中L+1~i 内的所有货物移动 i 处所消耗的费用

          不过对于将i+1~R区间的货物移动到i处所需的费用需要反过来减(思考一下就明白原因了~)

           所以将i+1~R中所有的货物移动到i处所需的费用为:   (f[R] - f[i]) - (sum[R] - sum[i])*d[i]

           对于  sum[R] - sum[L] - q 则是计算出区间[L,R]的货物总量比q=x多出的部分,按照当前是优先放弃R的原则我们可以知道:

区间L+1~R比x多的货物数从R处移动到i处的费用(sum[R] - sum[L] - q)*(d[R] - d[i]) 

           至于另外一个优先放弃左边界(L)的CNT_2函数自己推敲~(原理就是上面那样了)

代码如下:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<vector>
using namespace std;
#define ll long long
const int maxn = 5e5 + 10;
ll d[maxn];      //d[i]表示第i个集装箱距离x=0的距离
ll a[maxn];      //a[i]表示第i个集装箱有的货物个数
ll sum[maxn];    //sum[i]表示第1~i个集装箱有的货物数总和
ll f[maxn];      //f[i]表示第1~i个集装箱的所有货物移到x=0处所需费用
ll t, q;
int n, L, R, u;  //u为货物汇总的最优点
ll cnt_1(int i) {//费用=将L+1~i中所有货物移动到i处所需费用+(将i+1~R中所有的货物移动到i处所需的费用 - 区间L+1~R比x多的货物数从R处移动到i处的费用)
	return ((sum[i] - sum[L])*d[i] - (f[i] - f[L])) + ((f[R] - f[i]) - (sum[R] - sum[i])*d[i] - (sum[R] - sum[L] - q)*(d[R] - d[i]));
}
ll cnt_2(int i) {//同上,只是规则是优先从右边界开始取货物
	return -((sum[R] - sum[i])*d[i] - (f[R] - f[i])) - ((f[i - 1] - f[L]) - (sum[i - 1] - sum[L])*d[i] + (sum[R] - sum[L] - q)*(d[i] - d[L + 1]));
}
bool ok(ll x) {                                //判断需求x是否能在所给的费用t内达到
	q = x;              
	L = 0, R = 1, u = 1; 
	while (1) {                                //从左边界开始向右移动区间,优先取区间左边的货物
		while (R < n&&sum[R] - sum[L] < x) R++;
		if (sum[R] - sum[L] < x)break;         //若是当前的L~n无法满足x,那么L++也不可能满足
		while (u < L)u++;                      //将u移动到当前区间的起点
		while (u < R&&cnt_1(u)>cnt_1(u + 1))u++;
		if (cnt_1(u) <= t)return true;
		L++;
	}
	L = n - 1, R = n, u = n;
	while (1) {                                //从右边界开始向左移动区间,优先取区间右边的货物
		while (L > 0 && sum[R] - sum[L] < x) L--;
		if (sum[R] - sum[L] < x)break;         //若是当前的L~R无法满足x,那么R--也不可能满足
		while (u > R)u--;
		while (u > L && cnt_2(u) > cnt_2(u - 1))u--;
		if (cnt_2(u) <= t)return true;
		R--;
	}
	return false;
}
int main()
{
	scanf("%d%lld", &n, &t);
	t /= 2;
	for (int i = 1; i <= n; i++)
		scanf("%lld", &d[i]);
	for (int i = 1; i <= n; i++) {  //输出和前缀和预处理
		scanf("%lld", &a[i]);
		sum[i] = sum[i - 1] + a[i];
		f[i] = f[i - 1] + a[i] * d[i];
	}
	ll l = 0,r = sum[n] + 1;
	while (l + 1 < r) {              //二分答案
		ll mid = (l + r) >> 1;
		if (ok(mid)) l = mid;
		else r = mid;
	}
	printf("%lld\n", l);
	return 0;
}

        

猜你喜欢

转载自blog.csdn.net/weixin_41156591/article/details/81174029
今日推荐