Dirt Ratio
Time Limit: 18000/9000 MS (Java/Others) Memory Limit: 524288/524288 K (Java/Others)
Total Submission(s): 1364 Accepted Submission(s): 622
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
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
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.
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.
Source
2017 Multi-University Training Contest - Team 4
一道常规题,还是那句话,比赛的时候就是蠢……
本题是问,区间内 (题目数目)/(区间长度)的最小值。不巧的是,这一类问题比较就没有遇到了,所以说比较蠢,没有想到最优比例生成树这个经典模型。最优比例生成树是今年寒假的时候就见到的模型,大致就是每条边有两个权值ai和bi,然后要你求一棵生成树,使得最后树的 所有ai之和/所以bi之和最大或者最小。解决的方法就是用二分答案,我们假设这个最后的比例是k,为了方便表示我们把ai之和表示为sa,bi之和表示为sb,那么有:sa/sb=k。然后我们设最优解为ans,有sa/sb=ans,移项有sa-ans*sb=0,又因为ans优于k(我们这里假设求比例最小),那么k>ans,即sa-k*sb<0。如此一来,机智的人就可以发现,sa-k*sb可以成为我们判定的式子,如果说我们二分枚举的k代入这个式子之后结果小于等于零,说明这个k是合法的,而且最优解小于等于它。反之,如果大于零,说明这个解不合法,不可能产生这么小的比例。
然后这题也是类似的,根据题意我们可以得出以下式子:size(l,r)/(r-l+1)<=ans,其中size表示区间的不同的数字个数。即找到l、r使得比例最小,同样的进行变形得:size(l,r)+l*ans<=(r+1)*ans。我们二分枚举一个k,判断方法与上面说的类似。然后现在问题是,如何确定一个区间内不同的数字的个数。我们可以考虑用线段树+扫描线来解决这个问题。我们用每一个数字把区间分成很多个小段,然后从左到右依次扫描,扫描到某一个点之后,把以该点为右端点的线段加入线段树,即这条线段覆盖的区域的size值增加1。然后,我们线段树中每个点维护的就是那个size(l,r)+l*k,扫描到每一个i之后添加线段的同时,该点要加上l*k。之后,我们在看从1~i区间中的区间最小值,看是否已经小于等于(r+1)*k(这里相当于找一个左端点,然后对应的右端点是i),如果小于等于则当前k合法且最优解小于等于k,否则继续扫描直到扫描到n。如果到了n最小值还是不满足条件,说明当前k不合法。具体见代码:
#include<bits/stdc++.h>
#define eps 1e-4
#define N 60010
using namespace std;
int n,a[N],ls[N];
struct ST
{
struct node
{
double min,lazy;
int l,r;
} tree[N<<2];
inline void push_up(int i)
{
tree[i].min=min(tree[i<<1].min,tree[i<<1|1].min);
}
inline void build(int i,int l,int r)
{
tree[i].r=r;
tree[i].l=l;
tree[i].lazy=0;
if (l==r)
{
tree[i].min=0;
return;
}
int mid=(l+r)>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
push_up(i);
}
inline void push_down(int i)
{
tree[i<<1].lazy+=tree[i].lazy;
tree[i<<1|1].lazy+=tree[i].lazy;
tree[i<<1].min+=tree[i].lazy;
tree[i<<1|1].min+=tree[i].lazy;
tree[i].lazy=0;
}
inline void update(int i,int l,int r,double x)
{
if ((tree[i].l==l)&&(tree[i].r==r))
{
tree[i].lazy+=x;
tree[i].min+=x;
return;
}
if (tree[i].lazy!=0) push_down(i);
int mid=(tree[i].l+tree[i].r)>>1;
if (mid>=r) update(i<<1,l,r,x);
else if (mid<l) update(i<<1|1,l,r,x);
else
{
update(i<<1,l,mid,x);
update(i<<1|1,mid+1,r,x);
}
push_up(i);
}
inline double getmin(int i,int l,int r)
{
if ((tree[i].l==l)&&(tree[i].r==r)) return tree[i].min;
if (tree[i].lazy!=0) push_down(i);
int mid=(tree[i].l+tree[i].r)>>1;
if (mid>=r) return getmin(i<<1,l,r);
else if (mid<l) return getmin(i<<1|1,l,r);
else return min(getmin(i<<1,l,mid),getmin(i<<1|1,mid+1,r));
}
} seg;
int main()
{
int T_T;
cin>>T_T;
while(T_T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
double l=0,r=1,mid;
while(r-l>eps)
{
bool flag=0;
mid=(l+r)/2.0;
seg.build(1,1,n); //其实没有必要每次都重新建树,是需要情况lazy和min即可,但是我懒得改模板了
memset(ls,0,sizeof(ls));
for(int i=1;i<=n;i++)
{
seg.update(1,ls[a[i]]+1,i,1); //把以i为右端点的区间加1,表示size增加一个
seg.update(1,i,i,i*mid); ls[a[i]]=i; //把位置i加上i*mid,表示以i为左端点的贡献
if (seg.getmin(1,1,i)<=(i+1)*mid){r=mid;flag=1;break;} //判定解的合法性
}
if (!flag) l=mid;
}
printf("%.4f\n",r);
}
return 0;
}