性质:
将所有留下来的水从低到高排序,设 h i h _ i hi表示第 i i i杯水的高度, s i s _ i si表示前 i i i杯水的高度前缀和。
合并操作一定是堆在最后面的一段一段的区间,从前往后合并。
设 f ( i , j ) f(i,j) f(i,j)表示前 j j j杯水合并了 i i i次。
f ( i , j ) = m a x ( f ( i − 1 , k ) + s j − s k j − k + 1 ) f(i,j)=max(\frac{f(i-1,k)+s _ j-s _ k}{j-k+1}) f(i,j)=max(j−k+1f(i−1,k)+sj−sk)
观察这个式子,发现是点 ( k − 1 , s k − f ( i − 1 , k ) ) (k-1,s _ k - f(i-1,k)) (k−1,sk−f(i−1,k))和点 ( j , s j ) (j, s _ j) (j,sj)构成的直线的斜率。
用单调队列维护点 ( k − 1 , s k − f ( i − 1 , k ) ) (k-1,s _ k - f(i-1,k)) (k−1,sk−f(i−1,k))构成的下凸壳。因为点 ( j , s j ) (j, s _ j) (j,sj)的y坐标随x坐标递增,所以找最优决策时,将队首一段不会构成最优决策的点的弹出,队首就是最优决策。
那个600多行的高精度小数类就直接删掉了……只要记得将开头的const int PREC=
后面的数字改为3000以上即可。
将每一步的决策用short记录下来,然后用高精度小数类再算一次答案。
using namespace std;
#define RI register int
typedef long double db;
typedef long long LL;
const int N=8005;
Decimal ans;
int orzyyb,n,K,P,kpos;db kans;
int h[N],qid[N];LL s[N];short las[N][N];db f[2][N];
struct point{db x,y;}que[N];
point operator - (point A,point B) {return (point){A.x-B.x,A.y-B.y};}
db operator * (point A,point B) {return A.x*B.y-B.x*A.y;}
Decimal getans(int x,int y) {
if(x==0) return 0;
return (getans(x-1,las[x][y])+s[y]-s[las[x][y]])/(y-las[x][y]+1);
}
int main() {
scanf("%d%d%d",&orzyyb,&K,&P);
scanf("%d",&h[0]);
for(RI i=1;i<orzyyb;++i) {
int x;scanf("%d",&x);
if(x>h[0]) h[++n]=x-h[0];
}
sort(h+1,h+1+n);
for(RI i=1;i<=n;++i) s[i]=s[i-1]+h[i];
if(K>n) K=n;
for(RI i=1,t=1;i<=K;++i,t^=1) {
int he=1,ta=1;
que[1]=(point){(db)i-2,(db)s[i-1]-f[t^1][i-1]},qid[1]=i-1;
for(RI j=i;j<=n;++j) {
point P=(point){(db)j,(db)s[j]};
while(he<ta&&(P-que[he+1])*(P-que[he])<0) ++he;
las[i][j]=qid[he],f[t][j]=(db)(P.y-que[he].y)/(db)(P.x-que[he].x);
P=(point){(db)j-1,(db)s[j]-f[t^1][j]};
while(he<ta&&(que[ta]-que[ta-1])*(P-que[ta-1])<0) --ta;
++ta,que[ta]=P,qid[ta]=j;
}
if(f[t][n]>=kans) kans=f[t][n],kpos=i;
}
ans=getans(kpos,n)+h[0];
cout<<ans.to_string(P<<1)<<endl;
return 0;
}