transform-二分-尺取-贪心

  • https://ac.nowcoder.com/acm/contest/140/G
  • 题意:在一个数轴上有n个集装箱,第 i 个集装箱的位置为x[i],且在集装箱内装有a[i]件货物。
  • 现在将这些集装箱内的货物进行移动(将一件货物从第 i 个集装箱移动到第 j 个集装箱的花费就为2*abs(x[i]-x[j]) )。
  • 求在总花费不超过T的情况下,最多能将多少货物移动到同一个集装箱内。
  • 思路:既然要使得花费在不超过T的情况尽可能多的移动货物,那么我们肯定是将一个区间内的所有货物,
  • 移到坐标中位的集装箱上。那么我们就可以对答案进行二分,然后枚举所要移动的区间的左端点,
  • 再找到中位点和右端点,然后判断这个区间移动的花费是否小于T,之所以正反打两边尺取是因为,当数目是
  • 偶数时,中位数位置可能有多个选择,而且我们枚举的区间内的数目也不一定恰好等于当前而二分到的答案
  • 我们就默认从左端开始枚举区间时中位数位置选取靠左的,从右边枚举区间时,中位数选取位置靠右的。
  • 这样在处理 (区间内的数目也不一定恰好等于当前而二分到的答案)这种情况时,在两类枚举状态下分别有
  • 最优的选择,当从左边枚举时中位数选取靠左的,多出的数目自然是扔掉右边的 最优,
  • 同理,当从右边枚举时中位数选取靠右的,多出的数目自然是扔掉左边的 最优,
  • #include<bits/stdc++.h>
    using namespace std;
    #define maxn 555555
    #define ll long long
    int n,a[maxn],x[maxn];
    ll pr[maxn],s[maxn],t,l,r,mid;
    ll rpr[maxn],rs[maxn];
    ll precal(int l,int r)
    {
        return pr[r]-pr[l-1]-s[l-1]*(x[r]-x[l-1]);
    }
    ll lastcal(int l,int r)
    {
        return rpr[l]-rpr[r+1]-rs[r+1]*(x[r+1]-x[l]);
    }
    bool ok(ll cur)
    {
        ll sum=(cur+1)/2,mod,w;
        int md,head,tail;
        head=tail=md=1;
        while(1)
        {
            while(tail<=n&&s[tail]-s[head-1]<cur)tail++;
            while(md<=n&&s[md]-s[head-1]<sum)md++;
            if(tail>n||md>n)break;
            mod=s[tail]-s[head-1]-cur;
            w=precal(head,md)+lastcal(md,tail)-mod*(x[tail]-x[md]);
            if(w<=t)return true;
            head++;
        }
        head=tail=md=n;
        while(1)
        {
            while(head>=1&&rs[head]-rs[tail+1]<cur)head--;
            while(md>=1&&rs[md]-rs[tail+1]<sum)md--;
            if(head<1||md<1)break;
            mod=rs[head]-rs[tail+1]-cur;
            w=precal(head,md)+lastcal(md,tail)-mod*(x[md]-x[head]);
            if(w<=t)return true;
            tail--;
        }
        return false;
    }
    int main()
    {
        scanf("%d%lld",&n,&t);
        t/=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];
            pr[i]=pr[i-1]+s[i-1]*(x[i]-x[i-1]);
        }
        for(int i=n; i>=1; i--)
        {
            rs[i]=rs[i+1]+a[i];
            rpr[i]=rpr[i+1]+rs[i+1]*(x[i+1]-x[i]);
        }
        r=1e16;
        while(l+1<r)
        {
            mid=(l+r)/2;
            if(ok(mid))l=mid;
            else r=mid;
        }
        if(ok(r))printf("%lld\n",r);
        else printf("%lld\n",l);
        return 0;
    }
  •  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值