【BZOJ4654】【NOI2016】国王饮水记(动态规划,斜率优化)

57 篇文章 0 订阅

题面

BZOJ
洛谷

题解

首先肯定是找性质。
明确一点, h1 h 1 小的没有任何意义
所以我们按照 h h 排序,那么h1就是当前 1 1 号位置的水量。
假设我们使用的次数不受到任何限制,我们思考怎么样才是最优。
首先每次只和一个合并一定比和多个合并更优
假设有三个位置h1<h2<h3
那么如果直接合并,答案是 (h1+h2+h3)/3 ( h 1 + h 2 + h 3 ) / 3
如果每次合并一个,答案是 ((h1+h2)/2+h3)/2=(h1+h2)/4+h3/2 ( ( h 1 + h 2 ) / 2 + h 3 ) / 2 = ( h 1 + h 2 ) / 4 + h 3 / 2
显然后者更优。
同理,通过后面那个式子,我们发现先和 h2 h 2 合并比先和 h3 h 3 合并更优。
所以,在合并次数不受到限制的时候,我们显然是从小往大,依次合并

当次数不够的时候,我们肯定不能只合并一个位置,显然合并所有位置还是更优的。
那么,既然不能够一个个合并,所以只能够把若干次合并放在一起做。
因为每次和后面的若干个做完合并之后,这些一起合并的位置就可以直接丢掉了,
再因为从小往大合并更优,假设 hi h i 已经有序,那么每次一定是和一段合并。
所以设 f[i][j] f [ i ] [ j ] 表示前面 i i 个位置合并了j次的最优值。
那么考虑转移 f[i][j]=max((f[k][j1]+x[k+1,i]hx)/(ik+1) f [ i ] [ j ] = m a x ( ( f [ k ] [ j − 1 ] + ∑ x ∈ [ k + 1 , i ] h x ) / ( i − k + 1 )
这样的复杂度 O(n2k) O ( n 2 k ) k k 是可以合并的次数。
把式子重写,写成前缀和的形式,暂时忽略后面的枚举次数
f[i]=(f[k]+s[i]s[k])/(ik+1) f [ i ] = ( f [ k ] + s [ i ] − s [ k ] ) / ( i − k + 1 )
这个式子很像斜率:
(s[i](s[k]f[k]))/(i(k1)) ( s [ i ] − ( s [ k ] − f [ k ] ) ) / ( i − ( k − 1 ) )
相当于把前面所有已知状态看成一个点 (k1,s[k]f[k]) ( k − 1 , s [ k ] − f [ k ] )
那么找到当前点 (i,s[i]) ( i , s [ i ] ) 到这个点的斜率,使其斜率最大,可以斜率优化解决
这样可以在凸包上三分计算,时间复杂度 O(nklog3n) O ( n k l o g 3 n )
因为给定的高精小数库还有一个 O(p) O ( p ) 的复杂度,所以整个的复杂度是 O(nkplogn) O ( n k p l o g n )
同时还发现具有决策单调性,所以可以做到 O(nkp) O ( n k p )
决策单调性的证明大概是这样的,假设当位置是 (i,s[i]) ( i , s [ i ] )
最优转移是 (x1,y1) ( x 1 , y 1 ) ,存在一个不优的转移 (x2,y2) ( x 2 , y 2 )
根据题目条件,有 i>x1>x2,s[i]>y1>y2,s[i]>i,y1>x1,y2>x2 i > x 1 > x 2 , s [ i ] > y 1 > y 2 , s [ i ] > i , y 1 > x 1 , y 2 > x 2
一下个位置至少是 (i+1,s[i]+i) ( i + 1 , s [ i ] + i ) ,然后把斜率的式子写一写发现 (x1,y1) ( x 1 , y 1 ) 仍然更优。

现在可以做到 O(nkp) O ( n k p ) ,但是这样的复杂度远远不够。
继续挖掘性质。
发现有一个条件我们还没有怎么使用,即所有 hi h i 互不相等。
那么我们可以得到一个条件:每次转移的区间长度不会大于上一次转移的区间长度。
感性的证明就是越往后走高度越大,显然拿更少的位置来平分会更优。
或者可以假设一下区间的长度,然后计算一下结果就好了。
或者假如后面那个区间长度大于前面这个区间,那么把后面那个区间的最前面那个位置分给前面这一段一定更优。
还有一个奇怪的条件:长度大于 1 1 的区间个数不会超过O(lognhΔ),其中 Δ=min(hihi1) Δ = m i n ( h i − h i − 1 )
证明?我不会我不会可以参考这里的证明
那么这样子,只需要 dp d p 大概 14 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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值