[JZOJ 3432] 服务器(斜率优化DP常见问题&详细解答)

Description

我们需要将一个文件复制到n个服务器上,这些服务器的编号为S1, S2, …, Sn。
首先,我们可以选择一些服务器,直接把文件复制到它们中;将文件复制到服务器Si上,需要花费ci > 0的置放费用。对于没有直接被复制文件的服务器Si来说,它依次向后检查Si+1, Si+2, …直到找到一台服务器Sj:Sj中的文件是通过直接复制得到的,于是Si从Sj处间接复制得到该文件,这种复制方式的读取费用是j – i(注意j>i)。另外,Sn中的文件必须是通过直接复制得到的,因为它不可能间接的通过别的服务器进行复制。我们设计一种复制方案,即对每一台服务器确定它是通过直接还是间接的方式进行复制(Sn只能通过直接方式),最终使每一台服务器都得到文件,且总花费最小。

100%的数据中,1 <= n <= 1 000 000,1 <= ci <= 1 000 000 000

Analysis

比赛时我打的二维dp,可是许多人都是一维的,而且只有打一维的才可能想到正解——斜率优化。
一年没打斜率优化了,都快忘掉了QAQ,赶紧恶补了一下。
好吧,一维的dp式子是 f[i]=min(f[j]+(ji)(ji1)2+a[i]),j>i
这样做是 O(n2) 的,TLE。
对于当前的 i i 是从后往前枚举的),设有两个决策 j,k j 优于 k ,那么需要满足的条件是

f[j]+(ji)(ji1)2+a[i]<f[k]+(ki)(ki1)2+a[i]

移项,整理得
f[j]f[k]+j2k2j+k2jk<i

g(j,k) 表示不等式左边。
所以我们维护一个单调递减的队列,里面大概长成这个样子
i>g(jl,jl+1)>g(jl+1,jl+2)>>g(jr1,jr)

队头即 jl 为对于当前的 i 的最优解。
然后我们 i 是递减的,所以一旦有 i<g(jl,jl+1) 则令 jl 出队,因为这说明 jl 不再是最优的了。
而做完当前的 i 之后,也要将其加入队列。
若有 g(jr1,jr)<g(jr,i) 则说明 jr 永远不可能取到最优了,证明如下:
采用反证法,假设 jr 能够取到最优,则
jr 优于 jr1 等价于 g(jr1,jr)>i
jr 优于 i 等价于 g(jr,i)<i
综合上面两个不等式,得
g(jr,i)<i<g(jr1,jr)
惊奇地发现不等式出现了矛盾!
得证!
所以要把 jr 踢掉,一直做下去直到不满足上述条件,然后让 i 入队。
但是,naive的我曾经有过这样一个疑问,为什么不能直接用 g(i,jr) i 优于 jr 来判断队尾的出队情况呢?
因为当前 i 优于 jr 并不代表以后的 i 亦优于 jr (这个证明是感性的,凑合理解吧)。
好吧,斜率优化dp为什么起这个名呢?因为 g(j,k) 这个东西在平面图上长得很像 j,k 两点的斜率!

这个图够直观吧。

Code

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
typedef long long ll;
const int N=1000010;
int n;
ll f[N],q[N],a[N];
double G(ll j,ll k)
{
    return (f[j]-f[k]+(j*j-k*k-j+k)/2.0)*1.0/(j-k);
}
int main()
{
    scanf("%d",&n);
    fo(i,1,n) scanf("%lld",&a[i]);
    f[n]=a[n];
    int l=1,r=1;
    q[1]=n;
    fd(i,n-1,0)
    {
        while(l<r && i<G(q[l],q[l+1])) l++;
        f[i]=f[q[l]]+a[i]+(q[l]-i)*(q[l]-i-1)/2;
        while(l<r && G(q[r-1],q[r])<G(i,q[r])) r--;
        q[++r]=i;
    }
    printf("%lld",f[0]);
}

你可能感兴趣的:(dp,单调队列,斜率优化DP,决策单调性)