HDU 6070 Dirt Ratio(二分+线段树 17多校第四场)

  • 题目大意

    给定一个序列a[],让你求出一个子区间内不同元素个数除以区间长度的最小的一个值。 n6104 ,答案精度不高于 104

  • 分析

    题目要求的是:

    min{dif[l][r]rl+1}(1lrn,dif[l][r][l,r])

    最暴力的方法通过两层循环来枚举这个区间,外层循环是左端点,内层循环是右端点。
    类似于CF 833B,我们可以用一个数组 pre[x] 保存在 x 前面最近的和a[x]相等的数的下标,这样我们可以通过 O(1) 的复杂度从 dif[l][r] 推得 dif[l][r+1]
    同样类似于CF 833B,这样的 O(n2) 的复杂度还是会超时
    我们尝试去用一种数据结构来维护这个区间最小值
    我们可以 O(1) 的复杂度从 dif[l][r] 推得 dif[l][r+1] ,那么我们可以通过线段树以 O(logn) 的复杂度从
    dif[1...l][r] 推得 dif[1...l][r+1] ,但是这个区间信息是一个带除法运算的表达式我们很难对这个分母进行区间的更新操作
    如果我们能够将除法运算转化成加法运算

    如果我们想到了二分, min{dif[l][r]rl+1}mid 是否成立
    变换一下也就是 min{dif[l][r]+lmidmid}rmid 是否成立
    这样我们就能够对表达式 dif[l][r]+lmidmid 通过线段树来进行区间更新和查询操作了

  • 代码

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<map>
#include<algorithm>
#include<set>
#include<stack>
using namespace std;
const double INF=999999.0;
const long long int MOD=998244353;
const int MAXN=60005;
int T;
int n;
int a[MAXN];
int pre[MAXN];
int pos[MAXN];
double tree[MAXN*4];
double lazy[MAXN*4];
void PushUp(int rt)//将延迟标记上推
{
     tree[rt]=min(tree[rt*2],tree[rt*2+1]);
}
void PushDown(int rt)
{
    lazy[rt*2]+=lazy[rt];
    lazy[rt*2+1]+=lazy[rt];
    tree[rt*2]+=lazy[rt];
    tree[rt*2+1]+=lazy[rt];
    lazy[rt]=0;
    return ;
}
void Build(double x,int l,int r,int rt)
{
    lazy[rt]=0;
    if(l==r)
    {
        tree[rt]=(double)l*x-x;
        return ;
    }
    int m=(l+r)/2;
    Build(x,l,m,rt*2);
    Build(x,m+1,r,rt*2+1);
    PushUp(rt);
}
void Update(int L,int R,int l,int r,int rt)
{
    if(l>=L && r<=R)
    {
        lazy[rt]+=1;
        tree[rt]+=1;
        return ;
    }
    int m=(l+r)/2;
    PushDown(rt);
    if(L<=m)Update(L,R,l,m,rt*2);
    if(R>m)Update(L,R,m+1,r,rt*2+1);
    PushUp(rt);
}
double Query(int L,int R,int l,int r,int rt)
{
    if(l>=L &&r<=R)
    {
        return tree[rt];
    }
    double ans=INF;
    PushDown(rt);
    int m=(l+r)/2;
    if(L<=m)ans=min(ans,Query(L,R,l,m,rt*2));
    if(R>m)ans=min(ans,Query(L,R,m+1,r,rt*2+1));
    PushUp(rt);
    return ans;
}
bool Check(double x)//判断x作为答案是否可行
{
    Build(x,1,n,1);
    double minm=INF;
    for(int i=1;i<=n;i++)
    {
        Update(pre[i]+1,i,1,n,1);
        if(Query(1,i,1,n,1)<=(double)i*x){return 1;}
    }
    return 0;
}
void Work()
{
      double l=0;
      double r=1.0;
      double m;
      while(r-l>0.0000001)
      {
          m=(l+r)/2;
          if(Check(m))r=m;
          else l=m;
      }
      cout<<l<<endl;
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        memset(pos,0,sizeof(pos));
        memset(pre,0,sizeof(pre));
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            pre[i]=pos[a[i]];
            pos[a[i]]=i;
        }
        Work();
    }
    return 0;
}
/*
1
5
1 2 1 2 3
*/

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值