[题解] [JSOI2015] 送礼物
题面
题解
首先我们得先知道一个小结论, 就是说如果最优方案中最大值和最小值必定是在区间两端
要么就是这两个数在长度为 \(L\) 的区间中
那么我们用 ST表处理出长度为 \(L\) 的区间中最优的答案
接下来处理两个数在区间首尾的方案
假设区间右端是最大值, 左端是最小值
左端最大右端最小把数组 \(reverse\) 一下就行
考虑用上分数规划那套分析方法
\[\displaystyle\begin{aligned}\frac{M(l, r) - m(l, r)}{r - l + k}&\geq ans\\M(l, r) - m(l, r) &\geq (r - l + k) * ans\\(M(l, r) - r * ans) - (m(l, r) - l * ans) &\geq k * ans\\\end{aligned}
\]
如果确定了 \(ans\) , 那么左边的 \(M(l, r) - r * ans\) 就是关于位置 \(r\) 的一个定值
发现这个东西可以单调队列
做完了, 二分一下这个 \(ans\) 就行
Code
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
const int N = 50005;
const double eps = 1e-6;
using namespace std;
int T, n, m, L, R, a[N], f[2][15][N], q[N];
double res, ans, b[N];
template < typename T >
inline T read()
{
T x = 0, w = 1; char c = getchar();
while(c < '0' || c > '9') { if(c == '-') w = -1; c = getchar(); }
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * w;
}
void getst()
{
for(int i = 1; (1 << i) <= n; i++)
for(int j = 1; j + (1 << i) - 1 <= n; j++)
{
f[0][i][j] = min(f[0][i - 1][j], f[0][i - 1][j + (1 << (i - 1))]);
f[1][i][j] = max(f[1][i - 1][j], f[1][i - 1][j + (1 << (i - 1))]);
}
}
int query(int opt, int l, int r)
{
int tmp = log(1.0 * r - l + 1) / log(2.0);
if(opt) return max(f[1][tmp][l], f[1][tmp][r - (1 << tmp) + 1]);
else return min(f[0][tmp][l], f[0][tmp][r - (1 << tmp) + 1]);
}
bool check(double mid)
{
int l = 1, r = 0, flag = 0; q[++r] = 1;
for(int i = 1; i <= n; i++) b[i] = a[i] - mid * i;
for(int i = L; i <= n; i++)
{
if(b[i] - b[q[l]] >= mid * m) flag |= 1;
while(l <= r && q[l] < i + 2 - R) l++;
while(l <= r && b[q[r]] >= b[i - L + 2]) r--;
q[++r] = i - L + 2;
}
return flag;
}
void clear() { res = ans = 0; }
int main()
{
T = read <int> ();
while(T--)
{
clear(), n = read <int> (), m = read <int> (), L = read <int> (), R = read <int> ();
for(int i = 1; i <= n; i++)
f[0][0][i] = f[1][0][i] = a[i] = read <int> ();
getst();
for(int i = 1; i + L - 1 <= n; i++)
res = max(res, 1.0 * (query(1, i, i + L - 1) - query(0, i, i + L - 1)) / (L - 1 + m));
double l = 0, r = 1000, mid;
while(fabs(r - l) > eps)
{
mid = (l + r) / 2;
if(check(mid)) ans = mid, l = mid;
else r = mid;
}
res = max(res, ans);
reverse(a + 1, a + n + 1);
l = 0, r = 1000;
while(fabs(r - l) > eps)
{
mid = (l + r) / 2;
if(check(mid)) ans = mid, l = mid;
else r = mid;
}
printf("%.4lf\n", max(res, ans));
}
return 0;
}