Hdu 6070 Dirt Ratio【分数规划+二分+线段树】好题~好题~

142 篇文章 0 订阅
46 篇文章 0 订阅

Dirt Ratio

Time Limit: 18000/9000 MS (Java/Others)    Memory Limit: 524288/524288 K (Java/Others)
Total Submission(s): 1515    Accepted Submission(s): 698
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(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
  
  
1 5 1 2 1 2 3

题目大意:

让你找到一个区间:【L,R】,使得其中的数字的种类数/区间长度最小。

问这个比例最小是多少。


思路:


考虑分数规划:

①我们希望Val【L,R】/(R-L+1)最小。这里Val【L,R】表示区间中数字的种类数。

②我们不妨设定F(X)=Val【L,R】-X*(R-L+1);

③那么如果有F(X)<=0,那么一定有Val【L,R】/(R-L+1)<=X;

④所以我们这个最小的值可以通过二分来求出来。


那么我们如何check呢?


我们再转化一下不等式变成:Val【L,R】+mid*L<=(R+1)*mid.(这里X==mid);


那么我们O(n)枚举R,之前我们可以利用线段树来维护mid*L+Val【L,R】的最小值,那么如果查询Query(1,i-1)能够<=(R+1)*mid的话,就说明可以继续向下二分。

否则向上。


我们线段树更新mid*L很简单,一路update(i,i,mid*i)即可,那么Val【L,R】怎样处理呢?其实也不难,我们维护一个数组last【i】,表示数字i出现的最后的位子,那么对于当前数i,其影响的区间Val的值的范围就是从【last【a【i】】+1,i】,那么再update一下(last【a【i】】+1,i,1)即可。


尽量去for二分。


Ac代码:

#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
#define lson l,m,rt*2
#define rson m+1,r,rt*2+1
double tree[350050*8];
double flag[350050*8];
int last[350050];
int a[350050];
void build(int l,int r,int rt){
    flag[rt]=0;
    if(l==r)
    {
        tree[rt]=0;
        flag[rt]=0;
        return;
    }
    int m=(l+r)>>1;
    build(lson);
    build(rson);
}
void pushdown(int l,int r,int rt)//向下维护树内数据
{
    if(flag[rt])//如果贪婪标记不是0(说明需要向下进行覆盖区间(或点)的值)
    {
        int m=(l+r)/2;
        flag[rt*2]+=flag[rt];
        flag[rt*2+1]+=flag[rt];
        tree[rt*2]+=flag[rt];//千万理解如何覆盖的区间值(对应线段树的图片理解(m-l)+1)是什么意识.
        tree[rt*2+1]+=flag[rt];
        flag[rt]=0;
    }
}
void pushup(int rt)
{
    tree[rt]=max(tree[rt<<1],tree[rt<<1|1]);
}
double Query(int L,int R,int l,int r,int rt)
{
    if(L<=l&&r<=R)
    {
        return tree[rt];
    }
    else
    {
        pushdown(l,r,rt);
        int m=(l+r)>>1;
        double ans=-10000000000000;
        if(L<=m)
        {
            ans=max(ans,Query(L,R,lson));
        }
        if(m<R)
        {
            ans=max(ans,Query(L,R,rson));
        }
        pushup(rt);
        return ans;
    }
}
void update(int L,int R,double c,int l,int r,int rt)
{
    if(L<=l&&r<=R)//覆盖的是区间~
    {
        tree[rt]+=c;//覆盖当前点的值
        flag[rt]+=c;//同时懒惰标记~!
        return ;
    }
    pushdown(l,r,rt);
    int m=(l+r)/2;
    if(L<=m)
    {
        update(L,R,c,lson);
    }
    if(m<R)
    {
        update(L,R,c,rson);
    }
    pushup(rt);
}
int Slove(int n,double mid)
{
    build(1,n,1);
    for(int i=1;i<=n;i++)last[a[i]]=0;
    for(int i=1;i<=n;i++)
    {
        update(i,i,-mid*i,1,n,1);
        update(last[a[i]]+1,i,-1,1,n,1);
        last[a[i]]=i;
        if(i==1)continue;
        double Q=-Query(1,i-1,1,n,1);
        if(Q<=(i+1)*mid)return 1;
    }
    return 0;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)scanf("%d",&a[i]);
        double l=0;
        double r=1;
        double ans;
        for(int i=0;i<20;i++)
        {
            double mid=(l+r)/2;
            if(Slove(n,mid)==1)
            {
                ans=mid;
                r=mid;
            }
            else l=mid;
        }
        printf("%.10f\n",ans);
    }
}











  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值