Dirt Ratio
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
X
problems 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''.
Input
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.
1 5 1 2 1 2 3
0.5000000000
For every problem, you can assume its final submission is accepted.
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.
1 5 1 2 1 2 3
0.5000000000
For every problem, you can assume its final submission is accepted.
题目大意:给出一个长度为n的序列,对于每一段连续的区间 [l,r], 定义num[l,r]为区间中不同数字的个数,区间长度为len ,求num[l,r]/len的最小值;
题目分析:要最小化一个值,考虑二分。对于二分得到的 mid,要检查是否存在区间 [l,r], 满足num[l,r]/(r-l+1)<=mid
直接求得话,即使num[l,r]可以O(1)得到。也要枚举全部的O(n^2)个区间,时间不允许;
解决方法:可以考虑每次向右推进右端点,线段树维护 前面每个端点到右端点的值num[l,r]/len, 由于此时维护的是一个浮点数,更新的时候没
办法更新,如果维护分子分母两个值,又没法查询比值的最小值,这时解决的方法是化分式为整式,把分母乘过去,将原式变成
num [l,r] + mid*l<=mid*(r+1)
建立线段树,线段树的每个节点 a[l]=num[l,r]+ mid*l; mid*l是确定值,可以提前存入。 预处理一个数组pre[] ,pre[i]表示序列中第 i个数上
一次出现的位置,这样右端点 r每向右推进一位,只需更新 [ pre[r]+1 , r ]区间
检查每个答案的时间为O(nlogn),设二分次数为 k,则整体时间复杂度为 O(knlogn), 这道题的精度为10^-4, 大约二分二十次即可达到精度要求。
AC代码:其中代码大部分都是线段树的模板代码,这里不作赘述了。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<stdlib.h>
#include<algorithm>
using namespace std;
const int INFINITE=INT_MAX;
const int N=6e4+10;
struct SegTreeNode
{
double val;
int addMark;
}segTree[4*N]; ///开始忘了乘以4,一直wrong
double a[N];
void pushUp(int root)
{
segTree[root].val=min(segTree[root*2].val,segTree[root*2+1].val);
}
void build(int root,double a[],int istart,int iend)///递归建立线段树
{
segTree[root].addMark=0;
if(istart==iend)
{
segTree[root].val=a[istart];
}
else
{
int mid=(istart+iend)/2;
build(root*2,a,istart,mid);
build(root*2+1,a,mid+1,iend);
pushUp(root);
}
}
void pushDown(int root)
{
if(segTree[root].addMark!=0)
{
segTree[root*2].addMark+=segTree[root].addMark;
segTree[root*2+1].addMark+=segTree[root].addMark;
segTree[root*2].val+=segTree[root].addMark;
segTree[root*2+1].val+=segTree[root].addMark;
segTree[root].addMark=0;
}
}
void update(int root,int nstart,int nend,int ustart,int uend,int addVal)
{
if(ustart>nend||uend<nstart)
return ;
if(ustart<=nstart&&uend>=nend)
{
segTree[root].addMark+=addVal;
segTree[root].val+=addVal;
return ;
}
pushDown(root);
int mid=(nstart+nend)/2;
update(root*2,nstart,mid,ustart,uend,addVal);
update(root*2+1,mid+1,nend,ustart,uend,addVal);
pushUp(root);
}
double query_min(int root,int nstart,int nend,int qstart,int qend)
{
if(qstart>nend||qend<nstart)
return INFINITE;
if(qstart<=nstart&&qend>=nend)
return segTree[root].val;
pushDown(root);
int mid=(nstart+nend)/2;
return min(query_min(root*2,nstart,mid,qstart,qend),
query_min(root*2+1,mid+1,nend,qstart,qend));
}
int b[N],pre[N],pos[N],n;
bool check(double m)
{
for(int i=1;i<=n;i++)
a[i]=m*i;
build(1,a,1,n);
for(int i=1;i<=n;i++)
{
update(1,1,n,pre[i]+1,i,1);///更新区间[pre[i]+1,i]的值
double minx=query_min(1,1,n,1,i);///查找更新后的区间[1,i]中的最小值
if(minx<=m*(i+1))
return true;
}
return false;
}
int main()
{
int t;
cin>>t;
while(t--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",b+i);
memset(pos,0,sizeof(pos));
for(int i=1;i<=n;i++)
{
pre[i]=pos[b[i]];
pos[b[i]]=i;
}
double l=0,r=1;
for(int i=1;i<=20;i++)///二分法逐步逼近正确答案
{
double m=(l+r)/2;
if(check(m))
r=m;
else
l=m;
}
printf("%.5lf\n",r);
}
return 0;
}