这道题目比赛的时候做了半天,结果基本就是挂机的状态。事后看了题解也是一脸懵逼的。最后看了标程才知道怎么做的。看懂之后才发现这个这个题目那么有趣,真是思维太巧妙了(吐槽一下多校赛题解,只有做出了的人才能看懂。(ㄒoㄒ))。
他是这样解决的二分枚举答案,对于答案如果成立那么一定存在size(l, r)/(r-l+1) < = mid.所以化简得到size(l, r) + l * mid <= (r + 1) * mid.
如何在快速的解决这个呢?就是要用到线段树,首先初始化整个树的时候每个节点保存的是mid * l的值,然后取枚举右边界r。对于每个枚举的r的这种状态下,线段树(L, R)中存放的是,左区间在(L, R) 中,右区间为r的元素种类+l*mid的最小值。当r增加到r+1的时候(在处理的时候会有一个app数组记录当前元素出现的上一个位置),对于整个线段树的影响就是当前位置与当前元素出现的上一位置,这个区间加1。
对于二分的时间复杂度为logn,枚举右端点为n,对于每次枚举都要一次区间修改操作是logn,一次查询操作 logn.所以一共为o(n * logn * logn).
代码如下:
#include <iostream>
#include <stdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
#define Max_N (6*10000+100)
double v[4*Max_N];
int addv[4*Max_N];
int n;
int a[Max_N];
double min1 ;
void build(int node, int l, int r, double m) {
v[node] = m * l;
addv[node] = 0;
if (l == r) return;
int m1 = l + (r - l) / 2;
build(node*2, l, m1, m);
build(node * 2 + 1, m1 + 1, r, m);
}
int add1(int node, int x)
{
v[node] += x;
addv[node] += x;
}
void pushdown(int node)
{
if (addv[node]) {
add1(node * 2, addv[node]);
add1(node * 2 + 1, addv[node]);
}
addv[node] = 0;
}
void change(int node, int l, int r, int y1, int y2)
{
if (y1 <= l && r <= y2) {
add1(node, 1);
return;
}
pushdown(node);
int m = l + (r - l) / 2;
if (m >= y1) change(node * 2, l, m, y1, y2);
if (m+1 <= y2) change(node * 2 + 1, m + 1, r, y1, y2);
v[node] = min(v[node*2], v[node*2+1]);
}
double query(int node, int l, int r, int y1, int y2)
{
if (y1 > r || y2 < l) return 100000000;
if (r <= y2 && l >= y1) {
return v[node];
}
pushdown(node);
int m = l + (r - l) / 2;
return min(query(node * 2, l, m, y1, y2), query(node * 2 + 1, m + 1, r, y1, y2));
}
int app[Max_N];
int main()
{
int T;
cin >> T;
while (T--) {
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
double l = 0; double r = 1;
int flag = 0;
while (true) {
double mid = l + (r - l) / 2;
build(1, 1, n, mid);
flag = 0;
memset(app, 0, sizeof(app));
//cout << mid << endl;
for (int i = 1; i <= n; i++) {
change(1, 1, n, app[a[i]] + 1, i);
min1 = query(1, 1, n, 1, i);
if (min1 <= mid * (i*1.0 + 1)) {
flag = 1;
break;
}
app[a[i]] = i;
}
if (flag) r = mid;
else l = mid;
if (r - l <= 0.000001) break;
}
printf("%.10lf\n", (l + r) / 2);
}
return 0;
}