牛客网暑期ACM多校训练营(第二场)G transform 思维,二分
G transform
题意:
X轴上有 n 个箱子,在 X[i] 坐标的箱子里有 a[i] 个物品。把第 i 个箱子里的一个物品移动到第 j 个箱子,花费为 abs(X[i]-X[j]) 。可以在花费<=T/2 的情况下移动物品,最后要选择一个箱子取出它所有的物品,问最多可以取出多少个物品?
题解:
参考了题解https://www.nowcoder.com/discuss/88268?type=101&order=0&pos=1&page=1
1、首先明确所移动的物品一定是一个连续区间。可以这样想:假定我们最后选择取第 i 个箱子,那我们肯定是依次取它左右离它最近的箱子,这样是最优的,也就是一个区间。
2、选择了一个区间,那肯定是把物品移动到中间个数点最优,比如:10,1,1,1,1 中间个数点在第一个位置,肯定是把其它位置点移动到第一个位置最优。
理由:假如所有箱子只有一个物品,比如 1,1,1,1,1 那肯定移动到最中间最优。 我们可扩展想一下,一个箱子里有 a[i] 个物品,把它变为这个位置有 a[i] 个箱子,所以也是移动到最中间的箱子里。
3、知道前两点就可以二分求解了。二分答案,最多可以取 ans 个物品。每次二分枚举区间左端点 l ,然后移动右端点 r ,直到区间物品个数和 >=ans,然后看这个区间的最小花费是否<=T/2 。
求一个区间的最小花费,我们先枚举找出中间位置 mid,然后计算 区间 [l,mid] 所有物品移动到右端点的花费 + 区间 [mid,r] 所有物品移动到左端点的花费, 这即是最小花费。所以预处理一个前缀和就好了。
sum[i]表示前 i 个 a[i]*x[i] 的和 ,sumA[i]表示前 i 个 a[i] 的和, cal1(l,r) 表示区间 [l,r] 所有物品移动到左端点的花费,cal2(l,r)表示区间 [l,r] 所有物品移动到右端点的花费。
#include<bits/stdc++.h>
using namespace std;
#pragma comment(linker, "/STACK:102400000,102400000")
#define rep(i,a,b) for (int i=a; i<=b; ++i)
#define per(i,b,a) for (int i=b; i>=a; --i)
#define mes(a,b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define MP make_pair
#define PB push_back
#define fi first
#define se second
typedef long long ll;
const int N = 500005;
int n, a[N], X[N];
ll T, sum[N], sumA[N];
ll cal1(int l, int r) {
return sum[r] - sum[l-1] - (sumA[r]-sumA[l-1])*X[l];
}
ll cal2(int l, int r) {
return (sumA[r]-sumA[l-1])*X[r] - (sum[r]-sum[l-1]);
}
bool check(ll aim)
{
for(int l=1, r=1, mid=1; ; ++l)
{
while(r<=n && sumA[r]-sumA[l-1]<aim) ++r;
while(mid<=r && (sumA[mid]-sumA[l-1])*2<sumA[r]-sumA[l-1]) ++mid;
if(r > n) break;
ll tmp = sumA[r]-sumA[l-1]-aim;
if(cal2(l,mid)+cal1(mid,r)-tmp*(X[r]-X[mid]) <= T) return true;
}
for(int l=n, r=n, mid=n; ; --r)
{
while(l>0 && sumA[r]-sumA[l-1]<aim) --l;
while(l<=mid && (sumA[r]-sumA[mid-1])*2<sumA[r]-sumA[l-1]) --mid;
if(l < 1) break;
ll tmp = sumA[r]-sumA[l-1]-aim;
if(cal2(l,mid)+cal1(mid,r)-tmp*(X[mid]-X[l]) <= T) return true;
}
return false;
}
int main()
{
scanf("%d%lld", &n, &T); T>>=1;
rep(i,1,n) scanf("%d", &X[i]);
rep(i,1,n) {
scanf("%d", &a[i]);
sumA[i] = sumA[i-1] + a[i];
sum[i] = sum[i-1] + 1LL*a[i]*X[i];
}
ll l=0, r=1e10, mid, ans;
while(l <= r) {
mid = l+(r-l)/2;
if(check(mid)) ans=mid, l=mid+1;
else r=mid-1;
}
printf("%lld\n", ans);
return 0;
}