1. 程式人生 > >【BZOJ4654】【NOI2016】國王飲水記(動態規劃,斜率優化)

【BZOJ4654】【NOI2016】國王飲水記(動態規劃,斜率優化)

code 奇怪 while lib show ima double 優化 .com

【BZOJ4654】【NOI2016】國王飲水記(動態規劃,斜率優化)

題面

BZOJ
洛谷

題解

首先肯定是找性質。
明確一點,\(h_1\)小的沒有任何意義
所以我們按照\(h\)排序,那麽\(h_1\)就是當前\(1\)號位置的水量。
假設我們使用的次數不受到任何限制,我們思考怎麽樣才是最優。
首先每次只和一個合並一定比和多個合並更優
假設有三個位置\(h_1\lt h_2\lt h_3\)
那麽如果直接合並,答案是\((h_1+h_2+h_3)/3\)
如果每次合並一個,答案是\(((h_1+h_2)/2+h_3)/2=(h_1+h_2)/4+h_3/2\)
顯然後者更優。
同理,通過後面那個式子,我們發現先和\(h_2\)

合並比先和\(h_3\)合並更優。
所以,在合並次數不受到限制的時候,我們顯然是從小往大,依次合並

當次數不夠的時候,我們肯定不能只合並一個位置,顯然合並所有位置還是更優的。
那麽,既然不能夠一個個合並,所以只能夠把若幹次合並放在一起做。
因為每次和後面的若幹個做完合並之後,這些一起合並的位置就可以直接丟掉了,
再因為從小往大合並更優,假設\(h_i\)已經有序,那麽每次一定是和一段合並。
所以設\(f[i][j]\)表示前面\(i\)個位置合並了\(j\)次的最優值。
那麽考慮轉移\(f[i][j]=max((f[k][j-1]+\sum_{x\in[k+1,i]}h_x)/(i-k+1)\)


這樣的復雜度\(O(n^2k)\)\(k\)是可以合並的次數。
把式子重寫,\(\sum\)寫成前綴和的形式,暫時忽略後面的枚舉次數
\(f[i]=(f[k]+s[i]-s[k])/(i-k+1)\)
這個式子很像斜率:
\((s[i]-(s[k]-f[k]))/(i-(k-1))\)
相當於把前面所有已知狀態看成一個點\((k-1,s[k]-f[k])\)
那麽找到當前點\((i,s[i])\)到這個點的斜率,使其斜率最大,可以斜率優化解決
這樣可以在凸包上三分計算,時間復雜度\(O(nklog_3n)\)
因為給定的高精小數庫還有一個\(O(p)\)的復雜度,所以整個的復雜度是\(O(nkplogn)\)

同時還發現具有決策單調性,所以可以做到\(O(nkp)\)
決策單調性的證明大概是這樣的,假設當位置是\((i,s[i])\)
最優轉移是\((x1,y1)\),存在一個不優的轉移\((x2,y2)\)
根據題目條件,有\(i>x1>x2,s[i]>y1>y2,s[i]>i,y1>x1,y2>x2\)
一下個位置至少是\((i+1,s[i]+i)\),然後把斜率的式子寫一寫發現\((x1,y1)\)仍然更優。

現在可以做到\(O(nkp)\),但是這樣的復雜度遠遠不夠。
繼續挖掘性質。
發現有一個條件我們還沒有怎麽使用,即所有\(h_i\)互不相等。
那麽我們可以得到一個條件:每次轉移的區間長度不會大於上一次轉移的區間長度。
感性的證明就是越往後走高度越大,顯然拿更少的位置來平分會更優。
或者可以假設一下區間的長度,然後計算一下結果就好了。
或者假如後面那個區間長度大於前面這個區間,那麽把後面那個區間的最前面那個位置分給前面這一段一定更優。
還有一個奇怪的條件:長度大於\(1\)的區間個數不會超過\(O(log\frac{nh}{\Delta})\),其中\(\Delta=min(h_i-h_{i-1})\)
證明?我不會我不會,可以參考這裏的證明
那麽這樣子,只需要\(dp\)大概\(14\)層就好啦。(參考征途或者序列分割之類的題目)
完整的代碼戳這裏
閹割版本。畢竟放個700行的代碼翻都翻不完

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int MAX=8080;
Decimal ans;
int n,K,p,h[MAX],zy[MAX][15],s[MAX],tot;
int Q[MAX],H,T;
double f[MAX][15];
struct Node{double x,y;}q[MAX];
double Slope(Node a,Node b){return (a.y-b.y)/(a.x-b.x);}
Decimal Calc(int i,int j)
{
    if(!j)return h[1];
    return (Calc(zy[i][j],j-1)+s[i]-s[zy[i][j]])/(i-zy[i][j]+1);
}
int main()
{
    n=read();K=read();p=read();h[tot=1]=read();
    for(int i=2;i<=n;++i)
    {
        h[i]=read();
        if(h[i]>h[1])h[++tot]=h[i];
    }
    n=tot;sort(&h[1],&h[n+1]);
    for(int i=1;i<=n;++i)s[i]=s[i-1]+h[i];
    K=min(K,n);
    for(int i=1;i<=n;++i)f[i][0]=h[1];
    int lim=min(K,14);
    for(int j=1;j<=lim;++j)
    {
        Q[H=T=1]=1;
        for(int i=1;i<=n;++i)q[i]=(Node){i-1,s[i]-f[i][j-1]};
        for(int i=2;i<=n;++i)
        {
            Node u=(Node){i,s[i]};
            while(H<T&&Slope(u,q[Q[H]])<Slope(u,q[Q[H+1]]))++H;
            zy[i][j]=Q[H];f[i][j]=(s[i]-s[Q[H]]+f[Q[H]][j-1])/(i-Q[H]+1);
            while(H<T&&Slope(q[Q[T]],q[Q[T-1]])>Slope(q[Q[T]],q[i]))--T;
            Q[++T]=i;
        }
    }
    int m=n-K+lim,pos;double mx=0;
    for(int j=0;j<=lim;++j)
        if(f[m][j]>mx)mx=f[m][j],pos=j;
    ans=Calc(m,pos);
    for(int i=m+1;i<=n;++i)ans=(ans+h[i])/2;
    cout<<ans.to_string(p<<1)<<endl;
    return 0;
}

【BZOJ4654】【NOI2016】國王飲水記(動態規劃,斜率優化)