这道题的思路:
预处理出物品个数的前缀和、后缀和,以及 将1到i-1位置的物品全部搬到i位置的花费和 与 将n到n-i+1的物品全部搬到i位置的花费和,然后对要求的物品个数二分答案。
check的时候,因为物品个数确定,枚举左端点,左端点位置确定的时候右端点位置和中间值位置也确定,因此可以通过预处理的东西直接计算出全部搬到中间值位置的花费。
但是对于为什么只需要求左端点的箱子都取、右端点在的箱子取部分 和 右端点在的箱子都取、左端点在的箱子取部分就能得到最小花费我还无法理解。。。(纠结了很久了,就是想不清楚= =)
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define db double
#define m_p make_pair
#define p_b push_back
#define For(i,a,b) for(int i=a;i<=b;i++)
#define ls (root<<1)
#define rs ((root<<1)|1)
const int N=5e5;
const int M=1e4+5;
const db eps=1e-8;
const int INF=0x3f3f3f3f;
const int mod=1e7;
int n;
struct node{
ll x,a;
bool operator<(const node &p)const{
return x<p.x;
}
}a[N+5];
ll T,s[N+5],s1[N+5],pre[N+5],nex[N+5];
bool check(ll num){
ll lpos=1,rpos=1,mid=1;
ll num1=(num+1)/2;
while(rpos<=n&&mid<=n){
while(rpos<=n&&s[rpos]-s[lpos-1]<num){
rpos++;
}
if(rpos>n) break;
while(mid<=n&&s[mid]-s[lpos-1]<num1) mid++;
if(mid>n) break;
ll res=pre[mid]-pre[lpos]-2*s[lpos-1]*(a[mid].x-a[lpos].x);
res+=nex[mid]-nex[rpos-1]-2*s1[rpos]*(a[rpos-1].x-a[mid].x);
res+=2*(num-(s[rpos-1]-s[lpos-1]))*(a[rpos].x-a[mid].x);
if(res<=T) return 1;
lpos++;
}
rpos=n,mid=n,lpos=n;
while(lpos>=1&&mid>=1){
while(lpos>=1&&s1[lpos]-s1[rpos+1]<num) lpos--;
if(lpos<1) break;
while(mid>=1&&s1[mid]-s1[rpos+1]<num1) mid--;
if(mid<1) break;
ll res=pre[mid]-pre[lpos+1]-2*s[lpos]*(a[mid].x-a[lpos+1].x);
res+=nex[mid]-nex[rpos]-2*s1[rpos+1]*(a[rpos].x-a[mid].x);
res+=2*(num-(s[rpos]-s[lpos]))*(a[mid].x-a[lpos].x);
if(res<=T) return 1;
rpos--;
}
return 0;
}
int main(){
// freopen("in.txt","r",stdin);
scanf("%d %lld",&n,&T);
For(i,1,n) scanf("%lld",&a[i].x);
For(i,1,n) scanf("%lld",&a[i].a);
sort(a+1,a+1+n);
ll l=1,r=0,mid,ans=0;
For(i,1,n) r+=a[i].a,s[i]=r,pre[i]=2*s[i-1]*(a[i].x-a[i-1].x)+pre[i-1];
for(int i=n;i>=1;i--) s1[i]=s1[i+1]+a[i].a,nex[i]=nex[i+1]+2*s1[i+1]*(a[i+1].x-a[i].x);
while(l<=r){
mid=(l+r)>>1;
if(check(mid)) l=mid+1,ans=mid;
else r=mid-1;
}
cout<<ans<<"\n";
return 0;
}