牛客网暑期ACM多校训练营(第二场):G transform

题目大意:

有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");
}

猜你喜欢

转载自blog.csdn.net/f2935552941/article/details/81159031