1. 程式人生 > >2018牛客多校賽第二場 G transform(二分答案 + 字首和)

2018牛客多校賽第二場 G transform(二分答案 + 字首和)

大致題意: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<bits/stdc++.h>
#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]<x) ++r;
        while(mid<=r&&s[mid]-s[l-1]<(x+1)/2) mid++;
        if (r>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]<x) --l;
        while(mid>=l&&s[r]-s[mid-1]<(x+1)/2) mid--;
        if (l<=0||mid<l) break;
        LL delta=s[r]-s[l-1]-x;
        if (cal1(l,mid)+cal2(mid,r)-delta*(p[mid]-p[l])<=t) return 1;
        r--;
    }
    return 0;
}
 
int main()
{
    sf(n); sf(t); t/=2;
    for(int i=1;i<=n;i++) sf(p[i]);
    for(int i=1;i<=n;i++)
    {
        sf(a[i]);
        s[i]=s[i-1]+a[i];
        sum[i]=sum[i-1]+p[i]*a[i];
    }
    LL l=0,r=s[n],mid,ans=-1;
    while(l<=r)
    {
        mid=(l+r)>>1;
        if (check(mid)) l=mid+1,ans=mid;
                           else r=mid-1;
    }
    printf("%lld\n",ans);
    return 0;
}