2018牛客多校赛第二场 G transform(二分答案 + 前缀和)

2018牛客多校赛第二场 G transform(二分答案 + 前缀和)_第1张图片

 

 

大致题意:n个商店排成一列,对于每个商店i,他所在位置位pi,里面有ai个商品。现在你要在一个商店经营,位置任意选择。当你的商店东西卖完之后,你可以从旁边的商店调集商品到你的商店。每次调集一个商品的代价是距离的两倍。现在问你给你T这么多的钱,问你最多可以调集多少商品到你所在的商店。

朴素的想法就是暴力枚举你所在的位置,然后看T这么多钱最多可以从两边拿多少东西。我们可以二分我移动的半径,根据半径可以确定我从调集商品的商店的区间,然后判断是否合法并且维护最大值。这样二分需要一个log,找到对应的商店的区间也需要一个log,总的时间复杂度就是O(NlogNlogN),但对于本题5e5的数据范围,1s的时限来说有点紧张。我加了各种优化快读等才最终优化到900+ms。

下面说说正解。显然,对于一段商店的区间来说,如果我要把这些商店的东西全部都搬到一个商店的话,最小代价的搬运方法就是把所有物品搬到总物品数中位数所在的商店里面。所以说,我们可以考虑二分最后商品的答案。然后对于每一个答案,我们维护满足条件的区间[l,r]。首先,我们找到满足商品数大于二分答案mid的第一个区间,很容易知道其中位数所在的商店,计算所需要的搬运代价。如果小于T,那么直接返回,否则调整区间,左端点右移,对应调整右端点,知道找到下一个满足条件的区间。我们发现中位数所在的商店也是随着区间的右移而递增的,所以整个过程,每个点作为左端点一次,右端点一次,中位数0次或者1次,总的时间复杂度就是O(N)的。这样最后总的时间复杂度就是O(NlogN)的。少了一个log时间复杂度就稳定了很多。

最后注意由于中位数本身的性质,在偶数的时候中位数可以是两个,所以我在判定的时候,区间移动要做两次,一次从左移动到右,另一次从右边移动到左边。具体见代码:

#include
#define mod 998244353
#define LL long long
#define pb push_back
#define lb lower_bound
#define ub upper_bound
#define INF 0x3f3f3f3f
#define sf(x) scanf("%lld",&x)
#define sc(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define clr(x,n) memset(x,0,sizeof(x[0])*(n+5))
#define file(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
using namespace std;
 
const int N = 500010;
 
LL s[N],sum[N],a[N],p[N],n,t;
 
LL cal1(int l,int r)
{
    return (s[r]-s[l-1])*p[r]-(sum[r]-sum[l-1]);
}
 
LL cal2(int l,int r)
{
    return (sum[r]-sum[l-1])-(s[r]-s[l-1])*p[l];
}
 
bool check(LL x)
{
    LL l=1,r=1,mid=1;
    while(1)
    {
        while(r<=n&&s[r]-s[l-1]n||mid>r) break;
        LL delta=s[r]-s[l-1]-x;
        if (cal1(l,mid)+cal2(mid,r)-delta*(p[r]-p[mid])<=t) return 1;
        l++;
    }
    l=r=mid=n;
    while(1)
    {
        while(l>=1&&s[r]-s[l-1]=l&&s[r]-s[mid-1]<(x+1)/2) mid--;
        if (l<=0||mid>1;
        if (check(mid)) l=mid+1,ans=mid;
                           else r=mid-1;
    }
    printf("%lld\n",ans);
    return 0;
}

 

你可能感兴趣的:(二分答案)