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]+(j−i)∗(j−i−1)2+a[i]),j>i
这样做是
O(n2)
的,TLE。
对于当前的
i
(
f[j]+(j−i)∗(j−i−1)2+a[i]<f[k]+(k−i)∗(k−i−1)2+a[i]
移项,整理得
f[j]−f[k]+j2−k2−j+k2j−k<i
设 g(j,k) 表示不等式左边。
所以我们维护一个单调递减的队列,里面大概长成这个样子
i>g(jl,jl+1)>g(jl+1,jl+2)>⋯>g(jr−1,jr)
队头即 jl 为对于当前的 i 的最优解。
然后我们
而做完当前的 i 之后,也要将其加入队列。
若有
采用反证法,假设 jr 能够取到最优,则
jr 优于 jr−1 等价于 g(jr−1,jr)>i ,
jr 优于 i 等价于
综合上面两个不等式,得
g(jr,i)<i<g(jr−1,jr) 。
惊奇地发现不等式出现了矛盾!
得证!
所以要把 jr 踢掉,一直做下去直到不满足上述条件,然后让 i 入队。
但是,naive的我曾经有过这样一个疑问,为什么不能直接用
因为当前 i 优于
好吧,斜率优化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]);
}