题目大意:
有n个物品,每个物品有两个属性x[i],a[i],x[i]第i个容器当前的位置,a[i]表示当前位置容器内有多少物品。
其中把 i 容器内物品移动到 j 容器的代价是2*abs(x[j]-x[i]),求在花费不超过T的情况下,通过移动最多能使一个容器内的物品达到多少。
解题思路:
其实比赛看到这个题目就想到了一个性质,对于n个点,他们到某个点的位置和如果最小,那么那个点一定是中位数,当时大概想了一下这个题,然后发现J题好像可以做就是做J题了,结果J题也没做出来。。。
这道题目有两种做法,第一种是标称的二分答案,然后在数量不变的情况下通过O(n)的扫描判断出所有可能情况的花费并判断花费是否会出现小于T的情况。
第二种做法是大佬的做法,可以直接O(n)直接扫出答案。。。
这里用的是第一种,其实几乎就是照着标程写的,开始自己写的代码一直pass0,以为有什么问题,就不停照着标程改,最后发现是一个巨智障的错误,伤心= =
Ac代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e5+5;
const int INF=1e9+7;
int n,x[maxn],a[maxn];
ll m,s[maxn];
ll dis(int u,int v)
{
return (ll)(x[v]-x[u]);
}
ll vs(int l,int lc,int r,int rc)
{
if(l==r) return rc-lc;
else return s[r-1]-s[l]+a[l]-lc+rc;
}
bool check(ll cnt)
{
ll res=0,sum=0;
int l=1,r=n+1,lc=1,rc=1; //lc rc 表示l r边界所选的数 分别为 a[l]-lc+1 以及rc-1
for(int i=1;i<=n;i++) //以1为左端点的答案
{
if(sum+a[i]<=cnt) sum+=a[i],res+=a[i]*dis(1,i);
else
{
rc=cnt-sum+1,r=i,res+=(rc-1)*dis(1,i);
break;
}
}
if(res<=m) return 1;
for(int i=2;i<=n;i++) //不断枚举中位数
{
res+=dis(i-1,i)*(vs(l,lc,i,1)-vs(i,1,r,rc)); //更新res
while(r<=n&&dis(l,i)>dis(i,r)) //判断是否应该改变l r边界
{
int gk=min(a[l]-lc+1,a[r]-rc+1); //将lc rc更新
res+=gk*(dis(i,r)-dis(l,i));
lc+=gk,rc+=gk;
if(lc>a[l]) l++,lc=1;
if(rc>a[r]) r++,rc=1;
}
if(res<=m) return 1;
}
return 0;
}
int main()
{
scanf("%d%lld",&n,&m);m=m/2;
for(int i=1;i<=n;i++) scanf("%d",&x[i]);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),s[i]=s[i-1]+a[i];//前缀和
ll ans=0,L=0,R=s[n]; //二分答案
while(L<=R)
{
ll mid=(L+R)>>1;
if(check(mid)) ans=mid,L=mid+1;
else R=mid-1;
}
printf("%lld\n",ans);
//system("pause");
}