牛客网暑期ACM多校训练营(第二场) G (思维 +简单二分)

题目链接: 传送门
题意很简单 ,给你n个点,点的坐标为 x[i] ,每个点有 num[i] 个物品 把 在u位置的一个物品运到 v花费 2*abs(x[u]-x[v]) 在T花费内最多将多少物品移到一个点

可以直接二分答案,就是想办法把物品移动在o(1)的方法实现
开两个数组 d[i] 代表前d[i]个物品移到0的花费 这不难求 d[i]=d[i-1]+num[i]*x[i] 再开个数组记录 物品的前缀和,我直接把num数组处理了一下

把 [l r] 区间物品移动到 i 就是
1. [l+1,i] 把区间物品个数从0移动到i 减去 [l+1,i]区间物品移到0位置(多移动到)
2. [i+1,r] 同理得

这样就可以了,如何找中间值 i 因为移动随i 的增大肯定 先单减后单曾 直接找最低的就行
为什么要正反两边? 每次找到的物品数不一定正好k(二分的答案)个,所以得去掉几个,去掉的肯定是区间 右端点或左端点

代码
感觉不像正解,如果找i直接比较,会超时.....,和大佬代码时间相差好多...

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rd(a) scanf("%d",&a)
#define rlld(a) scanf("%lld",&a)
#define me(a,b) memset(a,b,sizeof(a))
const int maxn=5e5+10;
const ll mod =1e9+7;
ll num[maxn];
ll x[maxn];
ll d[maxn];
ll n;
ll t;
inline ll solve(int f,int i,int l,int r,int nu){
    if(!f){

    return -d[i]+d[l]+(num[i]-num[l])*x[i]+d[r]-d[l]-(num[r]-num[l]-nu)*x[r]-(d[i]-d[l])-(nu-num[i]+num[l])*x[i];
    }

    return d[r]-d[i]-(num[r]-num[i])*x[i+1]-d[r]+d[l]+(num[r]-num[l]-nu)*x[l+1]+(d[r]-d[i])+(nu-num[r]+num[i])*x[i+1];
}
bool judge(ll xx,ll limit){
    ll l=0;
    ll i=1;
    ll r=1;
    ll temp;
    while(1){
        while(r<=n&&num[r]-num[l]<xx) r++;
        if(num[r]-num[l]<xx) break; 
        temp=solve(0,i,l,r,xx);
        while(i<=n) {
            ll o=solve(0,i+1,l,r,xx);
            if(temp>=o)  {
                temp=o;i++;
            }
            else  break;

        }

        if(temp*2<=limit) return true;
        l++;
    }
    l=n-1;
    r=n;
    i=n-1;
    while(1){
        while(l>=0&&num[r]-num[l]<xx) l--;
        if(num[r]-num[l]<xx) break; 
            temp=solve(1,i,l,r,xx);
        while(i>0) {
            ll o=solve(1,i-1,l,r,xx);
            if(temp>=o)  {
                temp=o;i--;
            }
            else  break;

        }

        if(temp*2<=limit) return true;
        r--;        
    }


    return false;
}
int main(){


    rlld(n),rlld(t);
    for(int i=1;i<=n;i++) {
        rlld(x[i]);
    }

    for(int i=1;i<=n;i++) {
        rlld(num[i]);

    }   
    for(int i=1;i<=n;i++) 
    {       
            d[i]=d[i-1]+x[i]*num[i];
            num[i]+=num[i-1];
    }

    ll r=num[n];
    ll l=0;
    ll ans;
    while(l<=r){
        ll mid =(l+r)/2;
        if(judge(mid,t)) {
            l=mid+1;
            ans=mid;
        }
        else r=mid-1; 

    }

    printf("%lld",ans);

}

猜你喜欢

转载自blog.csdn.net/qq_37493070/article/details/81233420