[2018.4.23 T1] 数学

题目描述

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(n3k) 的大暴力, 成功get 15 points滚粗…

实际上, 是个正常人都可以写出 O ( n 2 ∗ k ) O(n^2*k) O(n2k)的大暴力…

我们设 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[i1][k]+val(k+1,j), k[i,j1]

其中 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[i1]+dat[i+1]sum[i+1]sum[i1]+...+dat[j]sum[j]sum[i1]

然后我们发现似乎出现了很多可以预处理的东西?比如 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[i1](p[j]p[i1])sum[i1]

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[i1][k]+h[j]h[k](p[j]p[k])sum[k], k[i,j1]

现在我们可以 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[i1][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]);
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值