题意:有n个位置,每个位置的坐标为x[i],有桶a[i]个。你现在要把若干个桶移动到同一个位置,求在移动总距离不超过T/2的情况下,最多可以将多少个木桶移动到同一个位置?(n<=5e5,T<=1e18,x[i]<=1e9,a[i]<=1e5)
思路:二分。如官方题解所说,我们要使移动的总距离最小,那么最终被移动的桶在数轴上一定是一段连续的区间。如果固定了这个区间,那么最优方案就是把这个区间的所有桶移动到这个区间的某个位置。而这个位置在这个区间内满足先单减再单增,在总体区间满足单调递增。我们固定这段连续区间的起点,那么区间的终点也是总体单增的。因此只需要O(n)枚举区间左端点。
我们用s[i]表示前i个位置有多少个桶,t[i]表示前i个位置的所有桶从0坐标移动过来的距离和。
假设我们二分到可以搬x个桶到同一个位置,当前判断的连续区间为[lp+1,rp],则首先s[rp]-s[lp]>=x。此时会有两种情况,就是我们在这个区间内要挑出x个桶。显然多余的桶一定在a[rp]或者在a[lp](因为我们单调枚举左端点)。当在a[rp]时,我们就不需要搬那多余的(s[rp]-s[lp]-x)个桶了。于是我们枚举这x个桶搬到某个位置x[i]时,区间下标[lp+1,i]的桶搬到x[i]的总距离为:
(s[i]-s[lp])*d[i]-(t[i]-t[lp]);
区间下标[i,rp]的桶搬到x[i]的总距离为(除去多余的桶和上一步已经搬了的桶):
t[rp]-t[lp]-(s[rp]-s[lp]-x)*d[rp]-(t[i]-t[lp])-(x-(s[i]-s[lp]))*d[i]; //之前红色的地方写成了d[i],感谢 乱世-繁华-梦 指正
以上式子需要好好思考一下。
当在a[lp]时与以上情况类似。此时需要从右往左枚举区间右端点。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=5e5+10;
int n;
ll T,o,p,lp,rp,a[maxn],d[maxn],t[maxn],s[maxn],mp;
ll c1(int i)
{
return (s[i]-s[lp])*d[i]-(t[i]-t[lp])+(o-t[i]+t[lp])-(p-s[i]+s[lp])*d[i];
}
ll c2(int i)
{
return -(s[rp]-s[i])*d[i]+(t[rp]-t[i])-(o-t[rp]+t[i])+(p-s[rp]+s[i])*d[i];
}
bool jud(ll x)
{
p=x;
lp=0;rp=1;mp=1;
while(1)
{
while(rp<n&&s[rp]-s[lp]<x) rp++;
if(s[rp]-s[lp]<x) break;
o=t[rp]-t[lp]-(s[rp]-s[lp]-x)*d[rp];
while(mp<n&&c1(mp)>c1(mp+1))mp++;
if(c1(mp)<=T)return 1;
lp++;
}
lp=n-1;rp=n;mp=n;
while(1)
{
while(lp>0&&s[rp]-s[lp]<x) lp--;
if(s[rp]-s[lp]<x) break;
o=t[rp]-t[lp]-(s[rp]-s[lp]-x)*d[lp+1];
while(mp>1&&c2(mp)>c2(mp+1))mp--;
if(c2(mp)<=T)return 1;
rp--;
}
return 0;
}
int main()
{
scanf("%d %lld",&n,&T);T>>=1;
for(int i=1;i<=n;i++) scanf("%lld",&d[i]);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
for(int i=1;i<=n;i++) {s[i]=s[i-1]+a[i];t[i]=t[i-1]+a[i]*d[i];}
ll l=0,r=s[n]+1;
while(l+1<r)
{
ll mid=(l+r)>>1;
if(jud(mid)) l=mid;
else r=mid;
}
printf("%lld\n",l);
return 0;
}