题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6070
题目大意:给定一个序列a,对于任何一个区间 [l,r],它的“Dirt Ratio”值为区间内不同元素的个数除以区间长度。现在要你求出序列a中哪个区间的“Dirt Ratio”值最小,输出最小值。
题目思路:由于是要找最小值,我们考虑用二分来找答案。假设当前二分的答案的值为 x ,如果这个序列满足这个答案的话,必然存在一个区间的 sum[i,j] / len[i,j] <= x,(sum[i,j] 表示区间 [i,j] 内不同元素的个数,len[i,j]为区间 [i,j] 的长度),则该式子可以化为
sum[i,j] - len[i,j] * x <= 0。
对于sum[i,j] 我们可以考虑用线段树来维护,用pre[i] 表示序列a中上一个值与a[i]相同的位置,然后再按顺序往线段树内加入a[i] ,每次对线段树的区间 [pre[i] + 1, i] 进行更新+1,这样加到第 i 个时,对于满足 j < i 的sum[j] 来说就表示着区间[j,i] 内不同元素的数的个数。
接下来就只用考虑 x 的影响,每次插入a[i]时,对于线段树区间 [1,i] 更新 -x,这样当更新到第 i 个元素时,线段树内的第 j 位的元素正好被减去了(i - j + 1)个x,正好是 j 到 i 的区间长度。这样当插入到第 i 个数,线段树内的第 j 位就代表着区间 [j,i] 的“Dirt Ratio”值,线段树内再维护一下区间最小值,这样顺序插入之后就可以枚举完所有区间了。注意一下二分的次数,如果太多次了就会TLE,由于精度只需要1e-4,所以二分15~20次就够了。
具体实现看代码:
#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <queue>
#include <vector>
#include <algorithm>
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pb push_back
#define MP make_pair
#define lowbit(x) x&-x
#define clr(a) memset(a,0,sizeof(a))
#define _INF(a) memset(a,0x3f,sizeof(a))
#define FIN freopen("in.txt","r",stdin)
#define IOS ios::sync_with_stdio(false)
#define fuck(x) cout<<"["<<#x<<" "<<(x)<<"]"<<endl
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int>pii;
const int MX = 60000 + 5;
int n, _;
int a[MX], pre[MX], last[MX];
double sum[MX << 2], lz[MX << 2];
void push_up(int rt) {
sum[rt] = min(sum[rt << 1], sum[rt << 1 | 1]);
}
void push_down(int rt) {
if (lz[rt]) {
lz[rt << 1] += lz[rt];
lz[rt << 1 | 1] += lz[rt];
sum[rt << 1] += lz[rt];
sum[rt << 1 | 1] += lz[rt];
lz[rt] = 0;
}
}
void build(int l, int r, int rt) {
sum[rt] = lz[rt] = 0;
if (l == r) return;
int m = (l + r) >> 1;
build(lson); build(rson);
push_up(rt);
}
void update(int L, int R, double d, int l, int r, int rt) {
if (L <= l && r <= R) {
sum[rt] += d;
lz[rt] += d;
return;
}
push_down(rt);
int m = (l + r) >> 1;
if (L <= m) update(L, R, d, lson);
if (R > m) update(L, R, d, rson);
push_up(rt);
}
double query(int L, int R, int l, int r, int rt) {
if (L <= l && r <= R) return sum[rt];
push_down(rt);
double res = 1e9;
int m = (l + r) >> 1;
if (L <= m) res = min(res, query(L, R, lson));
if (R > m) res = min(res, query(L, R, rson));
return res;
}
bool check(double x) {
build(1, n, 1);
for (int i = 1; i <= n; i++) {
update(pre[i] + 1, i, 1, 1, n, 1);
update(1, i, -x, 1, n, 1);
if (query(1, i, 1, n, 1) <= 0) return 1;
}
return 0;
}
int main() {
for (scanf("%d", &_); _; _--) {
scanf("%d", &n);
for (int i = 0; i <= n; i++) pre[i] = last[i] = 0;
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
pre[i] = last[a[i]];
last[a[i]] = i;
}
double l = 0, r = 1;
for (int i = 0; i < 15; i++) {
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
printf("%.5f\n", l);
}
return 0;
}