hdu-6070 _Dirt Ratio (二分答案+线段树维护)

来源:互联网 发布:windows安装清理工具 编辑:程序博客网 时间:2024/05/18 22:16

传送门:http://acm.hdu.edu.cn/showproblem.php?pid=6070

Dirt Ratio

Time Limit: 18000/9000 MS (Java/Others)    Memory Limit: 524288/524288 K (Java/Others)
Total Submission(s): 1350    Accepted Submission(s): 614
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''.
 

Input
The first line of the input contains an integer T(1T15), denoting the number of test cases.

In each test case, there is an integer n(1n60000) in the first line, denoting the length of the submission list.

In the next line, there are n positive integers a1,a2,...,an(1ain), 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 104.
 

Sample Input
151 2 1 2 3
 

Sample Output
0.5000000000
Hint
For every problem, you can assume its final submission is accepted.
 

Source
2017 Multi-University Training Contest - Team 4
 

Recommend
liuyiding
题目大意:有一组数列,求这组数列中的一个子数列使得这个子数列上不同的数字个数除以这个子序列的长度最小。

题解:

官方题解:

二分答案midmid,检验是否存在一个区间满足\frac{size(l,r)}{r-l+1}\leq midrl+1size(l,r)mid,也就是size(l,r)+mid×lmid×(r+1)

从左往右枚举每个位置作为rr,当rr变化为r+1r+1时,对sizesize的影响是一段区间加11,线段树维护区间最小值即可。 时间复杂度O(n\log n\log w)O(nlognlogw)

现在要找的是size(l,r)+mid*l的最小值。在维护线段树的时候,一开始先是一颗空树,然后每次加入一个数(从左到右,位置从1~n)就查询一下。

为什么这样呢?试想一下当我插入一个数的时候会对哪些区间造成影响,也就是哪些区间之前是没有当前插入的这个数的。那应该是当前插入的这个

数与上一次插入这个数的时候的位置的这段区间(记住我充计的是区间的不同数的个数),那么就对这段区间+1。然后再查询,查询的是以 i 为右端点

的最小值是多少。为什么只枚举R不枚举L呢?这里可以结合代码看一下,在线段树中当确定了R之后就会从线段树的根节点开始一直往下查找,知道R

不满足了位置,在这个过程中其实是对所有的L已经遍历了的,所以只需要枚举R就行了(挺巧妙的)。因为每添加一个数就对以当前添加的这个数的

位置为R进行一次查询,所以不仅会对所有的区间查询还对后面的没有影响(后面的数还没添加)。因为有精度,所以二分的次数有限。


#include <cstdio>#include <bits/stdc++.h>using namespace std;const int MAXN=6e4+100;int a[MAXN],tag[MAXN<<2],fipos[MAXN];double A[MAXN<<2],MID,mi;void build(int root,int l,int r){    A[root]=MID*l;    tag[root]=0;    if(l==r)    {        return ;    }    int mid=(l+r)>>1;    build(root<<1,l,mid);    build(root<<1|1,mid+1,r);    A[root]=min(A[root<<1],A[root<<1|1]);}void pushdown(int root){    if(tag[root])    {        tag[root<<1]+=tag[root];        tag[root<<1|1]+=tag[root];        A[root<<1]+=tag[root];        A[root<<1|1]+=tag[root];        tag[root]=0;    }}void change(int root,int l,int r,int L,int R){    if(L<=l&&r<=R)    {        tag[root]+=1;        A[root]+=1;        return ;    }    pushdown(root);    int mid=(l+r)>>1;    if(L<=mid)change(root<<1,l,mid,L,R);    if(R>mid)change(root<<1|1,mid+1,r,L,R);    A[root]=min(A[root<<1],A[root<<1|1]);}void query(int root,int l,int r,int pos){    if(r<=pos)    {        if(mi>A[root])mi=A[root];        return ;    }    pushdown(root);    int mid=(l+r)>>1;    query(root<<1,l,mid,pos);    if(pos>mid)query(root<<1|1,mid+1,r,pos);    ///如果pos>mid也要查pos<=mid的部分,因为当pos>mid时,那么pos<=mid的中的r都小于pos,要取最小值}int main(){    int T;    int i;    int n,K=20;    scanf("%d",&T);    while(T--)    {        scanf("%d",&n);        for(i=1; i<=n; i++)            scanf("%d",&a[i]);        double LL=0.0,RR=1.0;        for(int k=1; k<=K; k++)        {            MID=(LL+RR)/2;            build(1,1,n);            ///fipos[i]表示和i相等的前一个位置            for(i=1; i<=n; i++)fipos[i]=0;            for(i=1; i<=n; i++)            {                ///当插入a[i]时会对区间[fipos[a[i]]+1,i]有影响,进行更新                change(1,1,n,fipos[a[i]]+1,i);                mi=1e9;                ///查询区间以i为右端点的最小值                query(1,1,n,i);                if(mi-MID*(i+1)<=0)break;                fipos[a[i]]=i;            }            if(i<=n)RR=MID;            else LL=MID;        }        printf("%.10lf\n",(LL+RR)/2);    }    return 0;}