Dirt Ratio HDU - 6070

探讨ACM竞赛中团队表现指标DirtRatio的优化问题,通过算法寻找连续提交序列中最低DirtRatio,采用二分法与线段树实现高效求解。

                                      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 XX problems during the contest, and submitted YY times for these problems, then the ''Dirt Ratio'' is measured as XYXY. 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 nn 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 10410−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 nn 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 10410−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、付费专栏及课程。

余额充值