Dirt Ratio HDU - 6070

                                      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 X problems during the contest, and submitted  Y Y times for these problems, then the ''Dirt Ratio'' is measured as  XY 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) T(1≤T≤15), denoting the number of test cases. 

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

In the next line, there are  n n positive integers  a1,a2,...,an(1ain) 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  104 10−4.
Sample Input
1
5
1 2 1 2 3
Sample Output
0.5000000000

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

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

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

In the next line, there are  n n positive integers  a1,a2,...,an(1ain) 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  104 10−4.
Sample Input
1
5
1 2 1 2 3
Sample Output
0.5000000000

        
  
Hint
 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;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值