Dirt Ratio
Time Limit: 18000/9000 MS (Java/Others) Memory Limit: 524288/524288 K (Java/Others)Total Submission(s): 1026 Accepted Submission(s): 446
Special Judge
Problem Description
In ACM/ICPC contest, the ''Dirt Ratio'' of a team is calculated in the following way. First let's ignore all the problems the team didn't pass, assume the team passed Xproblems
during the contest, and submitted Y times
for these problems, then the ''Dirt Ratio'' is measured as XY.
If the ''Dirt Ratio'' of a team is too low, the team tends to cause more penalty, which is not a good performance.

Picture from MyICPC
Little Q is a coach, he is now staring at the submission list of a team. You can assume all the problems occurred in the list was solved by the team during the contest. Little Q calculated the team's low ''Dirt Ratio'', felt very angry. He wants to have a talk with them. To make the problem more serious, he wants to choose a continuous subsequence of the list, and then calculate the ''Dirt Ratio'' just based on that subsequence.
Please write a program to find such subsequence having the lowest ''Dirt Ratio''.

Picture from MyICPC
Little Q is a coach, he is now staring at the submission list of a team. You can assume all the problems occurred in the list was solved by the team during the contest. Little Q calculated the team's low ''Dirt Ratio'', felt very angry. He wants to have a talk with them. To make the problem more serious, he wants to choose a continuous subsequence of the list, and then calculate the ''Dirt Ratio'' just based on that subsequence.
Please write a program to find such subsequence having the lowest ''Dirt Ratio''.
Input
The first line of the input contains an integer T(1≤T≤15),
denoting the number of test cases.
In each test case, there is an integer n(1≤n≤60000) in the first line, denoting the length of the submission list.
In the next line, there are n positive integers a1,a2,...,an(1≤ai≤n), denoting the problem ID of each submission.
In each test case, there is an integer n(1≤n≤60000) in the first line, denoting the length of the submission list.
In the next line, there are n positive integers a1,a2,...,an(1≤ai≤n), denoting the problem ID of each submission.
Output
For each test case, print a single line containing a floating number, denoting the lowest ''Dirt Ratio''. The answer must be printed with an absolute error not greater than 10−4.
Sample Input
1 5 1 2 1 2 3
Sample Output
0.5000000000HintFor every problem, you can assume its final submission is accepted.
现在要你找出一个区间,使他的 AC率最小,也就是说要找一个区间,使它的长度又长,所含不同的数字个数又少,这样才是AC率最小的情况
题目要求的精度只有 10^-4 ,但是题目给的数据非常大,6万的数据量,没法暴力
思路:对于找答案的题目,往往可以考虑二分找答案,这道题就可以二分,我们二分答案 为 mid 然后检验是否存在一个区间满足 r−l+1size(l,r) ≤mid
可以把式子写成 size(l,r)+mid×l≤mid×(r+1)
然后可以用线段树去维护 size(l r)
通过观察发现 二分出了 mid 之后,我们可以先把 mid * l 在创建线段树的时候就直接计算出来 ,然后后期枚举区间的右极限就可以计算出不等式左边部分了,而右边部分是很
容易得出的,然后每次去比较再取最小值就可以得出答案了,由于题目要求的精度是10^-4 所以只需要二分二十次就达到精度了,不需要太多
代码如下
#include<stdio.h>
#define ll long long
#define MAXN 60005
double min(double a,double b){
return a < b ? a : b;
}
int a[MAXN],pre[MAXN],ql,qr;
double ans;
struct Tree{
double minv[4 * MAXN],Mid;
int addv[4 * MAXN];
void maintain(int id,int l,int r){
if(r > l){ // 有区间存在 取儿子里值小的那个
minv[id] = min(minv[2 * id],minv[2 * id + 1]);
}else{ // 没有的话 把它初始化为原来的 m * l
minv[id] = Mid * l;
}
if(addv[id]) // 答案就是要算 size(l,r) + m * l
minv[id] += addv[id]; // addv存的就是 size(l,r)
}
void pushdown(int id){ //把这个节点的统计赋值给儿子节点
addv[id * 2] += addv[id]; //懒惰数组,传递值给儿子
addv[id * 2 + 1] += addv[id];
addv[id] = 0;
}
void update(int id,int l,int r){
if(ql <= l && qr >= r){
addv[id]++; //如果当前的区间已经被目标区间所包含了,那么size(l,r)的值可以加一
}else{
pushdown(id); //否则把值传给儿子继续二分
int M = (l + r) / 2;
if(ql <= M){ //如果目标区间左极限在 二分值左边,那么继续二分
update(id * 2,l,M);
}else{ //如果不是,那么二分到这就可以了,去计算值
maintain(id * 2,l,M);
}
if(qr > M){ //这里同上
update(id * 2 + 1,M + 1,r);
}else{
maintain(id * 2 + 1,M + 1,r);
}
}
maintain(id,l,r); // 防止没有计算到的部分
}
void query(int id,int l,int r,int add){
if(ql <= l && qr >= r){ // 当前 l r 在 目标 ql qr之内的时候就去取小的答案
ans = min(ans,minv[id] + add); //多增加的数 使 size(l,r) 增长
}else{ //否则去二分
int M = (l + r) / 2;
if(ql <= M) //二分的时候保留 size(l,r)
query(id * 2,l,M,add + addv[id]);
if(qr > M)
query(id * 2 + 1,M + 1,r,add + addv[id]);
}
}
void build(int id,int l,int r){
if(l == r){ //到达叶子节点就计算 (m * l)
minv[id] = Mid * l;
addv[id] = 0; //初始化 记录 size(l,r) 的数组
return ;
}
int M = (l + r) / 2;
build(id * 2,l,M);
build(id * 2 + 1,M + 1,r);
minv[id] = min(minv[id * 2],minv[id * 2 + 1]);
addv[id] = 0;
}
}tree;
int main(){
int t;
scanf("%d",&t);
while(t--){
int n;
scanf("%d",&n);
for(int i = 1;i <= n;i++){
scanf("%d",&a[i]);
}
double l = 0,r = 1;
for(int i = 0;i < 20;i++){
double m = tree.Mid = (l + r) / 2.0; //二分枚举答案
tree.build(1,1,n); //建树 在建树同时就计算出 ( m * l )
for(int j = 1;j <= n;j++) //把 pre[]初始化为 0
pre[a[j]] = 0; //这个数组的目的是使在一个区间内没个数的贡献都只为 1
int flag = 0;
for(int x = 1;x <= n;x++){ //枚举右极限
ql = pre[a[x]] + 1; //标记下这个区间的左极限 和 右极限
qr = x;
tree.update(1,1,n); //处理 size(l,r)
pre[a[x]] = x; //相同数字做记录
ans = 1e9; //初始化为一个非常小的数
ql = 1; //前面处理完了,去查找的时候要全部查找,所以赋值 1到 x
qr = x;
tree.query(1,1,n,0); //从 1到x去查找
if(ans <= m * (x + 1)){ //如果结果小于这个答案,那么答案还可以更小,继续二分
flag = 1;
break;
}
}
if(flag)
r = m;
else
l = m;
}
printf("%.10f\n",l);
}
return 0;
}