洛谷 P1721 [NOI2016]国王饮水记

显然,高度小于 \(h_1\) 的要舍去。剩下的排序。
猜每次与 \(1\) 合并的都是连续的一段。这样就可以列出方程

\[dp_{k, i} = \max\{\frac{dp_{k - 1, j} + s_i - s_j} {i - j + 1}\} \]

发现可以维护一个凸包。

#include <cstdio>
#include <iostream>
#include <algorithm>

//decimal lib included

const int MAXN = 8e3 + 19;

int n, k, p;
int h[MAXN], cnt, s[MAXN];
double dp[MAXN][MAXN];
std::pair<int, int> pre[MAXN][MAXN];
int q[MAXN], head, tail;
int u, v;

Decimal dfs(int a, int b){
	if(a == 0)
		return Decimal(h[1]);
	return (dfs(pre[a][b].first, pre[a][b].second) + s[b] - s[pre[a][b].second]) / (b - pre[a][b].second + 1);
}

int main(){
	std::scanf("%d%d%d", &n, &k, &p);
	std::scanf("%d", h + ++cnt);
	for(int i = 2; i <= n; ++i){
		std::scanf("%d", h + ++cnt);
		if(h[cnt] <= h[1])
			--cnt;
	}
	std::sort(h + 1, h + 1 + cnt);
	for(int i = 1; i <= cnt; ++i)
		s[i] = s[i - 1] + h[i];
	int lim = std::min(k, n - 1);
	for(int i = 1; i <= cnt; ++i)
		dp[0][i] = h[1];
	for(int b = 1; b <= lim; ++b){
		dp[b][1] = h[1]; q[head = tail = 0] = 1;
		for(int i = 2; i <= cnt; ++i){
			dp[b][i] = dp[b][i - 1], pre[b][i] = pre[b][i - 1];
			while(
				head < tail &&
				(dp[b - 1][q[head + 1]] + s[i] - s[q[head + 1]]) / (i - q[head + 1] + 1) >=
				(dp[b - 1][q[head]] + s[i] - s[q[head]]) / (i - q[head] + 1)
			)
				++head;
			double res = (dp[b - 1][q[head]] + s[i] - s[q[head]]) / (i - q[head] + 1);
			if(res > dp[b][i])
				dp[b][i] = res, pre[b][i] = std::make_pair(b - 1, q[head]);
			while(
				head < tail &&
				(s[q[tail]] - dp[b - 1][q[tail]] - s[q[tail - 1]] + dp[b - 1][q[tail - 1]]) / (q[tail] - q[tail - 1]) >=
				(s[i] - dp[b - 1][i] - s[q[tail - 1]] + dp[b - 1][q[tail - 1]]) / (i - q[tail - 1])
			)
				--tail;
			q[++tail] = i;
		}
		if(dp[b][cnt] >= dp[u][v])
			u = b, v = cnt;
	}
	std::cout << dfs(u, v).to_string(p + 1)) << std::endl;
	return 0;
}

猜你喜欢

转载自www.cnblogs.com/natsuka/p/12717717.html