Description
我们需要将一个文件复制到n个服务器上,这些服务器的编号为S1, S2, …, Sn。
首先,我们可以选择一些服务器,直接把文件复制到它们中;将文件复制到服务器Si上,需要花费ci > 0的置放费用。对于没有直接被复制文件的服务器Si来说,它依次向后检查Si+1, Si+2, …直到找到一台服务器Sj:Sj中的文件是通过直接复制得到的,于是Si从Sj处间接复制得到该文件,这种复制方式的读取费用是j – i(注意j>i)。另外,Sn中的文件必须是通过直接复制得到的,因为它不可能间接的通过别的服务器进行复制。我们设计一种复制方案,即对每一台服务器确定它是通过直接还是间接的方式进行复制(Sn只能通过直接方式),最终使每一台服务器都得到文件,且总花费最小。
Solution
首先,我相信各位巨佬都可以一眼秒出时间复杂度
O
(
n
2
)
O(n^2)
O(n2)的dp。
不过我这个蒟蒻还是要多嘴一下,dp转移式就是:
f
[
i
]
=
m
i
n
(
f
[
i
]
,
f
[
j
]
+
(
j
−
i
)
∗
(
j
−
i
−
1
)
2
+
c
[
i
]
)
f[i]=min(f[i],f[j]+\frac{(j-i)*(j-i-1)}{2}+c[i])
f[i]=min(f[i],f[j]+2(j−i)∗(j−i−1)+c[i])
是不是很简单呢?
只可惜,这么美妙的一个方法,是会TLE的。
所以我们考虑优化。
要是有些斜率优化底子的人应该会想到斜率优化。
首先,如果从
j
j
j这个位置转移到
i
i
i这个位置要比从
k
k
k这个位置转移要优,那么一定会满足下面的这个式子:
f
[
j
]
+
(
j
−
i
)
∗
(
j
−
i
−
1
)
2
+
c
[
i
]
<
f
[
k
]
+
(
k
−
i
)
∗
(
k
−
i
−
1
)
2
+
c
[
i
]
f[j]+\frac{(j-i)*(j-i-1)}{2}+c[i]<f[k]+\frac{(k-i)*(k-i-1)}{2}+c[i]
f[j]+2(j−i)∗(j−i−1)+c[i]<f[k]+2(k−i)∗(k−i−1)+c[i]
化简一下
(
j
−
i
)
∗
(
j
−
i
−
1
)
2
\frac{(j-i)*(j-i-1)}{2}
2(j−i)∗(j−i−1)和
(
k
−
i
)
∗
(
k
−
i
−
1
)
2
\frac{(k-i)*(k-i-1)}{2}
2(k−i)∗(k−i−1)可得:
f
[
j
]
+
j
2
2
+
j
2
−
i
j
+
i
2
2
−
i
2
<
f
[
k
]
+
k
2
2
+
k
2
−
i
k
+
i
2
2
−
i
2
f[j]+\frac{j^2}{2}+\frac{j}{2}-ij+\frac{i^2}{2}-\frac{i}{2}<f[k]+\frac{k^2}{2}+\frac{k}{2}-ik+\frac{i^2}{2}-\frac{i}{2}
f[j]+2j2+2j−ij+2i2−2i<f[k]+2k2+2k−ik+2i2−2i
把两边只跟
i
i
i有关的全不消掉,得:
f
[
j
]
+
j
2
2
+
j
2
−
i
j
<
f
[
k
]
+
k
2
2
+
k
2
−
i
k
f[j]+\frac{j^2}{2}+\frac{j}{2}-ij<f[k]+\frac{k^2}{2}+\frac{k}{2}-ik
f[j]+2j2+2j−ij<f[k]+2k2+2k−ik
把跟
i
i
i有关的移到一边,把只跟
j
j
j有关或只跟k有关的移到一边,得:
f
[
j
]
+
j
2
2
+
j
2
−
f
[
k
]
−
k
2
2
−
k
2
<
i
j
−
i
k
f[j]+\frac{j^2}{2}+\frac{j}{2}-f[k]-\frac{k^2}{2}-\frac{k}{2}<ij-ik
f[j]+2j2+2j−f[k]−2k2−2k<ij−ik
把右边的
i
i
i提出来,得:
f
[
j
]
+
j
2
2
+
j
2
−
f
[
k
]
−
k
2
2
−
k
2
<
(
j
−
k
)
i
f[j]+\frac{j^2}{2}+\frac{j}{2}-f[k]-\frac{k^2}{2}-\frac{k}{2}<(j-k)i
f[j]+2j2+2j−f[k]−2k2−2k<(j−k)i
把
(
j
−
k
)
(j-k)
(j−k)除过去另一边,得:
f
[
j
]
+
j
2
2
+
j
2
−
f
[
k
]
−
k
2
2
−
k
2
(
j
−
k
)
<
i
\frac{f[j]+\frac{j^2}{2}+\frac{j}{2}-f[k]-\frac{k^2}{2}-\frac{k}{2}}{(j-k)}<i
(j−k)f[j]+2j2+2j−f[k]−2k2−2k<i
这样,是不是就很眼熟了呢?这就是我们所熟悉的斜率,然后搞个单调队列,下凸壳,便可以直接维护了。
Code
#include<cstdio>
#include<algorithm>
#define ll long long
#define ldb long double
using namespace std;
const int N=1e6+5;
ll n,head,tail;
ll a[N],f[N],ans,q[N];
ldb slope(ll x,ll y) {return (f[x]-f[y]+(x*x-y*y-x+y)*1.0/2)*1.0/(x-y)*1.0;}//斜率
int main() {
scanf("%lld",&n);
for(ll i=1;i<=n;i++)scanf("%lld",&a[i]);
q[0]=n;f[n]=a[n];
ans=a[n]+(n*(n-1)/2);
for(ll i=n-1;i;i--) {
while(head<tail&&slope(q[head],q[head+1])>=i)head++;//如果大于等于i就是q[head]没有q[head+1]优
ll j=q[head];
f[i]=f[j]+(j-i)*(j-i-1)/2+a[i];
ans=min(ans,f[i]+(i*(i-1)/2));
while(head<tail&&slope(q[tail],i)>=slope(q[tail-1],q[tail]))tail--;//下凸壳
q[++tail]=i;
}
printf("%lld",ans);
return 0;
}