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

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

#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;
}
posted @ 2018-06-28 17:03  Maxwei_wzj  阅读(88)  评论(0编辑  收藏  举报