Codeforces Round #643 (Div. 2) E.Restorer Distance(三分)

題目

n(1<=n<=1e5)的數組a[],第i個數爲ai(0<=ai<=1e9),每次操作你可以選以下三種中的一種

①選擇i,令ai+1,代價爲A

②選擇i,令ai-1,代價爲R

③選擇i,j,令其中一個-1,另一個+1,代價爲M

0<=A,R,M<=1e4,求最終的數組所有值都相等的最小代價

思路來源

https://www.cnblogs.com/1024-xzx/p/12926595.html

https://www.cnblogs.com/Kaike/p/12918815.html

題解

如果只有A和R,顯然可以從一邊往中間推,第i個向第i+1個轉移時,

增量爲i*A,減量爲(n-i)*R,如果可以推就推,這樣就能推到最小點,是開口向上的函數

類似的,若M合適,則最終會只使用AM或RM,同樣是兩個開口向上的函數

而最終的答案,是這三個函數取min,容易發現也是存在一個最小值點的,可三分

因爲最小值只有一個點,這裏記錄一下三分的兩種寫法吧

 

官方題解給了一個口胡的證明,設上升個數p和下降個數q,分p<=q和p>q討論

再設當前高度h,設當前小於h的個數爲x,則大於等於的爲x+1,

考慮h到h+1的變化,發現代價變化量爲x的一次函數,而x發生變化當且僅當在初始高度分界點

說明當p<=q時爲若干段分段線性函數,p>q同理,

題解還說明當二者之間變化時還會出現一個極值點\frac{\sum a_{i}}{n}(沒太看懂),和所有初始分界點(極值點)取小即可

 

另外,通過以上分析,可以判斷是一個連續函數導數線性變化,最多變號一次的單峯函數,

那麼分段函數也有如此特性,結合題目性質發現開口向上,果斷三分

代碼1

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int n,add,sub,mix,h[N],l,r;
ll cal(int x){
    ll y=0,z=0,sm,v;//add sub
    for(int i=1;i<=n;++i){
        if(h[i]<x)y+=x-h[i];
        else z+=h[i]-x;
    }
    sm=min(y,z);y-=sm;z-=sm;
    v=y*add+z*sub+sm*mix;
    //printf("x:%d cal:%lld\n",x,v);
    return v;
}
int main()
{
    scanf("%d",&n);
    scanf("%d%d%d",&add,&sub,&mix);
    mix=min(mix,add+sub);
    l=1e9+1;r=-1;
    for(int i=1;i<=n;++i){
        scanf("%d",&h[i]);
        l=min(l,h[i]);r=max(r,h[i]);
    }
    while(r-l>2){//考慮[1,3]時m=mm 但1爲最小點
        int m=(l+r)/2,mm=(m+r)/2;//m=mm時有r-l<=2
        if(cal(m)<cal(mm))r=mm-1;
        else l=m+1;
    }
    ll ans=cal(l);
    for(int i=l+1;i<=r;++i){
        ans=min(ans,cal(i));
    }
    printf("%lld\n",ans);
	return 0;
}

代碼2

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int n,add,sub,mix,h[N],l,r;
ll cal(int x){
    ll y=0,z=0,sm,v;//add sub
    for(int i=1;i<=n;++i){
        if(h[i]<x)y+=x-h[i];
        else z+=h[i]-x;
    }
    sm=min(y,z);y-=sm;z-=sm;
    v=y*add+z*sub+sm*mix;
    //printf("x:%d cal:%lld\n",x,v);
    return v;
}
int main()
{
    scanf("%d",&n);
    scanf("%d%d%d",&add,&sub,&mix);
    mix=min(mix,add+sub);
    l=1e9+1;r=-1;
    for(int i=1;i<=n;++i){
        scanf("%d",&h[i]);
        l=min(l,h[i]);r=max(r,h[i]);
    }
    while(l<r){
        int lm=l+(r-l)/3,rm=r-(r-l)/3;//m=mm時有r-l<=2
        if(cal(lm)<cal(rm))r=rm-1;
        else l=lm+1;
    }
    printf("%lld\n",cal(l));
	return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章