【JZOJ 3432】服务器 斜率优化DP常见问题&详细解答

来源:互联网 发布:淘宝为啥不卖斐讯 编辑:程序博客网 时间:2024/06/05 02:14

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。
对于当前的ii是从后往前枚举的),设有两个决策j,kj优于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]);}
0 0
原创粉丝点击