题目描述
Shy 有一个长度为 n 的数组 a[i],让你把这个数组分成 k 份(每份包含至少一个元素,且每份中的元素连续)。假设其中一份长这个样子,b[1], b[2],…,b[m],那么他的价值是b[1]/b[1]+(b[1]+b[2])/b[2]+…+(b[1]+b[2]+…+b[m])/b[m],求在所有的合法的划分中,最小的价值和是多少。
输入格式
第一行两个整数 n,k 表示数组长度和分成的份数;
第二行为n个整数。
输出格式
输出一个数表示答案。(要求与标准答案的绝对或相对误差不差过 1/10000)。
输入样例
4 2
100 3 5 7
输出样例
5.7428571429
数据规模
对于 30%的数据,1≤n≤1000;
对于 100%的数据,1≤n≤200000,1≤k≤min(50,n),1≤a[i]≤100000。
解题分析
博主在考试的时候脑抽了, 只写了一个 O ( n 3 ∗ k ) O(n^3*k) O(n3∗k) 的大暴力, 成功get 15 points滚粗…
实际上, 是个正常人都可以写出 O ( n 2 ∗ k ) O(n^2*k) O(n2∗k)的大暴力…
我们设 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示已经分了 i i i块, 截止第 k k k个位置的最优值, 那么我们有如下转移方程:
d p [ i ] [ j ] = m i n d p [ i − 1 ] [ k ] + v a l ( k + 1 , j ) , k ∈ [ i , j − 1 ] dp[i][j] = min_{dp[i-1][k] + val(k+1,j)}, \ k\in [i,j-1] dp[i][j]=mindp[i−1][k]+val(k+1,j), k∈[i,j−1]
其中 v a l ( i , j ) val(i,j) val(i,j)表示第 i i i至第 j j j位元素组成的块的值。
但是这样算每个块的贡献值是 O ( l e n ) O(len) O(len)的, 所以我们考虑如何优化。
v a l ( i , j ) = d a t [ i ] d a t [ i ] + d a t [ i ] + d a t [ i + 1 ] d a t [ i + 1 ] + . . . + ∑ n = i j d a t [ j ] val(i,j)=\frac {dat[i]}{dat[i]}+\frac {dat[i]+dat[i+1]}{dat[i+1]}+...+\frac {\sum _{n=i}^{j}}{dat[j]} val(i,j)=dat[i]dat[i]+dat[i+1]dat[i]+dat[i+1]+...+dat[j]∑n=ij
很明显上面是个前缀和, 那么我们考虑用前缀和表示一下:
v a l ( i , j ) = s u m [ i ] − s u m [ i − 1 ] d a t [ i ] + s u m [ i + 1 ] − s u m [ i − 1 ] d a t [ i + 1 ] + . . . + s u m [ j ] − s u m [ i − 1 ] d a t [ j ] val(i,j)=\frac {sum[i]-sum[i-1]}{dat[i]}+\frac {sum[i+1]-sum[i-1]}{dat[i+1]}+...+\frac {sum[j]-sum[i-1]}{dat[j]} val(i,j)=dat[i]sum[i]−sum[i−1]+dat[i+1]sum[i+1]−sum[i−1]+...+dat[j]sum[j]−sum[i−1]
然后我们发现似乎出现了很多可以预处理的东西?比如 s u m [ i ] d a t [ i ] \frac {sum[i]}{dat[i]} dat[i]sum[i], 我们预处理为一个前缀和为 h [ i ] h[i] h[i], 还有 1 d a t [ i ] \frac {1}{dat[i]} dat[i]1, 我们也预处理为一个前缀和 p [ i ] {p[i]} p[i]。
然后这个块的贡献就变成了
v a l ( i , j ) = h [ j ] − h [ i − 1 ] − ( p [ j ] − p [ i − 1 ] ) ∗ s u m [ i − 1 ] val(i,j)=h[j]-h[i-1]-(p[j]-p[i-1])*sum[i-1] val(i,j)=h[j]−h[i−1]−(p[j]−p[i−1])∗sum[i−1]
dp状态转移方程为
d
p
[
i
]
[
j
]
=
m
i
n
d
p
[
i
−
1
]
[
k
]
+
h
[
j
]
−
h
[
k
]
−
(
p
[
j
]
−
p
[
k
]
)
∗
s
u
m
[
k
]
,
k
∈
[
i
,
j
−
1
]
dp[i][j] = min_{dp[i-1][k]+h[j]-h[k]-(p[j]-p[k])*sum[k]}, \ k\in [i,j-1]
dp[i][j]=mindp[i−1][k]+h[j]−h[k]−(p[j]−p[k])∗sum[k], k∈[i,j−1]
现在我们可以 O ( 1 ) O(1) O(1)查询块的贡献,我们就有30points啦233
我们设 f ( k ) = d p [ i − 1 ] [ k ] − h [ k ] + p [ k ] ∗ s u m [ k ] f(k)=dp[i-1][k]-h[k]+p[k]*sum[k] f(k)=dp[i−1][k]−h[k]+p[k]∗sum[k]
那么当有一个转移位置 n n n比 k k k优的时候有:
f ( k ) − p [ j ] ∗ s u m [ k ] > f ( n ) − p [ j ] ∗ s u m [ n ] f(k)-p[j]*sum[k]>f(n)-p[j]*sum[n] f(k)−p[j]∗sum[k]>f(n)−p[j]∗sum[n]
移项得
f ( n ) − f ( k ) s u m [ n ] − s u m [ k ] > p [ j ] \frac {f(n)-f(k)}{sum[n]-sum[k]}>p[j] sum[n]−sum[k]f(n)−f(k)>p[j]
然后容易发现 p [ j ] p[j] p[j]这玩意是递增的…然后就可以斜率优化啦!
PS:%%%考场就做出此题的王义夫Dark lao
代码如下:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <algorithm>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define db double
#define MX 200005
db h[MX], p[MX], dp[2][MX], s[MX];
int que[MX];
int dot, cut;
IN db cal(const int &ly, const int & from)
{
return dp[ly][from] - h[from] + s[from] * p[from];
}
IN db get(const int &ly, const int &now)
{
return (cal(ly, que[now + 1]) - cal(ly, que[now])) / (s[que[now + 1]] - s[que[now]]);
}
int main(void)
{
R int i, j, k, ly, head, tail, noww;
db now, jcq;
scanf("%d%d", &dot, &cut);
for (i = 1; i <= dot; ++i)
{
scanf("%lf", &now);
s[i] = s[i - 1] + now;
jcq = 1.0 / now;
p[i] = p[i - 1] + jcq;
h[i] = h[i - 1] + s[i] / now;
}
ly = 1;
for (i = 1; i <= dot; ++i) dp[0][i] = h[i];
for (i = 2; i <= cut; ++i)
{
ly ^= 1;
noww = ly ^ 1;
head = tail = 0;
for (j = 1; j <= dot; ++j)
{
W (head < tail && get(ly, head) < p[j]) head++;
dp[noww][j] = cal(ly, que[head]) + h[j] - s[que[head]] * p[j];
W (head < tail && get(ly, tail - 1) > (cal(ly, j) - cal(ly, que[tail])) / (s[j] - s[que[tail]])) tail--;
que[++tail] = j;
}
}
printf("%.8lf", dp[noww][dot]);
}