题意:在一条线上有n个点,第i个点的位置为Xi,并且这个点上有ai个物品,把一个物品从xi位置搬到yi位置的花费是2*abs(xi-yi),问在总花费不超过T的情况下最多能把多少个物品搬到同一个位置。
题解:因为花费是由所有点到一个点的距离决定的,所以最后的答案一定是在一个连续的区间里面尽可能多的讲两边的物品移到中位点上。在知道总物品数量以后,可以考虑对答案进行二分,判断每个查找的答案是否合法。而二分查找初始的左端点是1(不用搬动物品花费为0),右端点是总的物品数量v,也是最多能放到同一个点的物品数量。我们每次判断的是中间值是否合法,如果合法,那么肯定还可以搬动更多,就继续在右边查找,否则的话就要减少答案,在左边查找。对于一个区间的花费由于我们是分为左右两部份来求的,所以要计算左端点到中点的花费和右端点起到中点的花费。
①. 定义prev[i]数组为从1~i的位置的总的物品总数,prex[i]为把从1~i的位置的物品都搬到i位置的花费。(前缀)
那么prev[i]=pre[i-1]+a[i] ,
prex[i]=prex[i-1]+prev[i-1]*(x[i]-x[i-1]);//相当于把i-1前面的物品都移动到i-1这个位置然后再将i-1这个位置上的所有物品搬到第i个位置
那么将区间(l , r)中的物品都搬到 r的花费就是:prex[r]-prex[l-1]-prev[l]*(x[r]-x[l-1])
②. 定义sufv[i]数组为从n~i的位置的总的物品总数,sufx[i]为把从n~i的位置的物品都搬运到i位置的花费。(后缀)
那么sufv[i]=sufv[i+1]+a[i],
sufx[i]=sufx[i+1]+sufv[i+1]*(x[i+1]-x[i]);//相当于把i+1后面的物品都移动到i+1这个位置然后再将i+1这个位置上的所有物品搬到第i个位置
那么将区间(l , r)中的物品都搬到l的花费就是:sufx[l]-sufx[r+1]-sufv[r+1]*(x[r+1]-x[l])
得到了以上式子我们就可以不断的枚举所要移动的区间的左端点,再找到中位点和右端点,分别用前缀和后缀计算左端点和右端点到中位点的花费,判断是否合法再进行移动。二分判断的过程中,由于最终的答案不一定是将整个区间内所有的货物都移动到一个集装箱,所以我们还要判断最后剩余的货物是从最左端的集装箱移动到中位点还是从最右端移动到中位点,然后再根据花费的大小情况进行二分就可以得到最终答案
附上代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=500010;
int n;
typedef long long ll;
ll t;
struct node
{
int v,x;
bool operator <(const node&aa)const
{
return aa.x>x;
}
} a[maxn];
ll prevv[maxn];//prevv[i]表示从1~i有多少个货物,
ll prex[maxn];//prex[i]表示把1~i的货物全部搬到i的花费
ll sufv[maxn];//sufv[i]表示从i~n有多少个货物
ll sufx[maxn];//sufx[i]表示把i~n的货物全部搬到n的花费
ll culp(int l,int r)
{
return prex[r]-prex[l-1]-(prevv[l-1]*(a[r].x-a[l-1].x));
}
ll culs(int l,int r)
{
return sufx[l]-sufx[r+1]-(sufv[r+1]*(a[r+1].x-a[l].x));
}
int check(ll num)//枚举的答案
{
ll num1=num/2+1;
int l=1,r=2,mid=2;//这里的l和r是位置
while(1)
{
while(r<=n&&prevv[r]-prevv[l-1]<num)//还没有满足
r++;
while(mid<=n&&prevv[mid]-prevv[l-1]<num1)
mid++;
if(r>n||mid>n) break;
ll ans=culp(l,mid)+culs(mid,r-1)+(num-(prevv[r-1]-prevv[l-1]))*(a[r].x-a[mid].x);
if(ans<=t)
return 1;
l++;
}
l=n-1;
r=n;
mid=n;
while(1)
{
while(l>=1&&prevv[r]-prevv[l-1]<num)
l--;
while(mid>=2&&prevv[mid]-prevv[l-1]<num1)
mid--;
if(l<1||mid<2) break;
ll ans=culp(l+1,mid)+culs(mid,r)+(num-(prevv[r]-prevv[l]))*(a[mid].x-a[l].x);
if(ans<=t)
return 1;
r--;
}
return 0;
}
int main()
{
memset(prevv,0,sizeof(prevv));
memset(prex,0,sizeof(prex));
memset(sufv,0,sizeof(sufv));
memset(sufx,0,sizeof(sufx));
scanf("%d%lld",&n,&t);
ll l=1,r=0,mid;
t/=2;
for(int i=1; i<=n; i++)
{
scanf("%d",&a[i].x);//距离
}
for(int i=1; i<=n; i++)
scanf("%d",&a[i].v);//每个点的物品数量
sort(a+1,a+n+1);//按照位置前后来排序;
for(int i=1; i<=n; i++)
{
prevv[i]=prevv[i-1]+a[i].v;
prex[i]=prevv[i-1]*abs(a[i].x-a[i-1].x)+prex[i-1];
r+=a[i].v;
}
for(int i=n; i>=1; i--)
{
sufv[i]=sufv[i+1]+a[i].v;
sufx[i]=sufv[i+1]*abs(a[i].x-a[i+1].x)+sufx[i+1];
}
while(l<=r)//这里枚举的左右端点和中间点都是能搬运的货物的数量
{
mid=(l+r)/2;
if(check(mid)) l=mid+1;
else r=mid-1;
}
printf("%lld\n",r);
return 0;
}