【BZOJ2216】Lightning Conductor(POI2011)-决策单调性优化DP

测试地址:Lightning Conductor
题目大意:给定一个数列 A A ,对每个Ai求一个最小的非负整数 p p ,使得对于所有j AjAi+p|ij| A j ≤ A i + p − | i − j |
做法:本题需要用到决策单调性优化DP。
今天终于学习了真·决策单调性优化DP,比较开心。这种优化的优化对象通常是我们所说的1D/1D状态转移方程,即形如:
f(i)=min{f(j)+w(j,i)} f ( i ) = min { f ( j ) + w ( j , i ) }
或:
f(i)=min{g(j)+w(j,i)} f ( i ) = min { g ( j ) + w ( j , i ) }
这类状态转移,若 w(i,j) w ( i , j ) 满足某些性质,我们就能用决策单调性优化DP。其实 min min max max 都可以,这里先讨论 min min max max 同理。
这个性质是: w(i,j+1)w(i,j)w(i+1,j+1)w(i+1,j) w ( i , j + 1 ) − w ( i , j ) ≥ w ( i + 1 , j + 1 ) − w ( i + 1 , j )
一般我们认为这个条件是满足四边形不等式的充要条件,但这里主要不是考虑四边形不等式的问题。观察上面的式子,我们发现 w w 实际上满足的是,在i增大时,同样的 j j j+1的变化率不断变小。这就意味着,一但存在一个时刻,使得对于 j<k j < k f(j)+w(j,i)>f(k)+w(k,i) f ( j ) + w ( j , i ) > f ( k ) + w ( k , i ) ,那么 j j 就没用了,因为w(j,i)增长总是比 w(k,i) w ( k , i ) 增长快。于是我们证明出了决策单调性,即对于 j<k j < k ,使得 f(j),f(k) f ( j ) , f ( k ) 最小的 s(j),s(k) s ( j ) , s ( k ) 满足 s(j)<s(k) s ( j ) < s ( k )
有了决策单调性,首先想到的就是缩小枚举范围。然而在没有其他限制条件的情况下,直接枚举最坏的复杂度仍然是 O(n2) O ( n 2 ) ,并没有起到任何实质上的优化。于是我们反过来考虑,不是考虑“使得 f(i) f ( i ) 最小的决策变量 s(i) s ( i ) 是多少”,而是考虑”一个决策变量 s s 能使得哪些f(i)最小”。我们需要维护一个队列 t t ,队列里每一项都是一个区间,表示某个决策变量最优的区间。当我们加入一个新的决策变量时,在队列中从后往前判断,如果新增决策变量在这个区间的左端点上已经比之前的决策变量优了,我们就把当前区间删掉,继续向前判断。当我们停住的时候,显然决策变量的更改点在区间的内部,因为一系列的优秀性质所以我们可以二分,于是对应地更新即可。
因此,决策单调性优化DP的时间复杂度是O(nlogn)的。
说了这么多,回到本题。移项后得到 pAj+|ij|Ai p ≥ A j + | i − j | − A i ,于是我们需要求出右边的最大值,这样就得到了上面方程的第二种形式。我们显然可以求出每个点左边和右边的决策点,最后取个 max max 即可。我们可以用类似的思路证明决策单调性(注意上面所有式子的符号在此时应该相反,因为 min min 变成了 max max ),然后使用上面的方法优化即可。时间复杂度为 O(nlogn) O ( n log ⁡ n )
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
int n,q[500010],l[500010],r[500010];
double a[500010],f[500010],g[500010];

double calc(int l,int r)
{
    return a[l]+sqrt(abs(r-l))-a[r];
}

int solve(int k,int l,int r,int i)
{
    while(l<r)
    {
        int mid=(l+r)>>1;
        if (calc(k,mid)>calc(i,mid))
            l=mid+1;
        else r=mid;
    }
    return l;
}

void dp(double *f)
{
    int h=1,t=0;
    for(int i=1;i<=n;i++)
    {
        l[h]++;
        if (h<=t&&r[h]<l[h]) h++;
        if (h>t||calc(i,n)>calc(q[t],n))
        {
            while(h<=t&&calc(i,l[t])>calc(q[t],l[t])) t--;
            if (h>t) q[++t]=i,l[t]=i,r[t]=n;
            else
            {
                int tmp=solve(q[t],l[t],n,i);
                r[t]=tmp-1;
                q[++t]=i,l[t]=tmp,r[t]=n;
            }
        }
        f[i]=calc(q[h],i);
    }
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%lf",&a[i]);

    dp(f);
    for(int i=1;i<n-i+1;i++)
        swap(a[i],a[n-i+1]);
    dp(g);
    for(int i=1;i<=n;i++)
        printf("%d\n",max(0,(int)ceil(max(f[i],g[n-i+1]))));

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值