HDU - 6070 线段树 + 分数规划

做多校的时候只想到用线段树维护sz【L,R】的值,一顿瞎想全都是复杂度爆表的。。。。。。。还是太弱。

废话不说,先看题意,令F【L,R】为L,R区间内数字种数/数字总个数,求最小的F【L,R】。

即最小的【L,R】/(R-L+1)。

mid>=sz【L,R】/(R-L+1),化为 sz【L,R】+L*mid<=(R+1)*mid,很明显的分数规划了。
二分答案,枚举左端点R,然后用线段树维护区间的sz【L,R】+L*mid的最小值。每加入一个R,更新【pre【R】+1,R】,pre【R】为上一次出现R的位置。若线段树一个节点的值为5,6,7,该节点维护【5,R】,【6,R】,【7,R】的sz【L,R】+L*mid最小值。复杂度为O(knlong),k为二分次数。

#include <bits/stdc++.h>
#define eps 0.00001
#define MAXN 60010
#define INF 999999999
using namespace std ;
double sum[MAXN<<2],add[MAXN<<2];
int n,last[MAXN],pre[MAXN];
void pushup(int rt)
{
    sum[rt]=min(sum[rt<<1],sum[rt<<1|1]);
}
void build(int rt,int l,int r,double mid)
{
    add[rt]=0;
    if(l==r)
    {
        sum[rt]=1.0*mid*l;
        return ;
    }
    int m=(l+r)>>1;
    build(rt<<1,l,m,mid);
    build(rt<<1|1,m+1,r,mid);
    pushup(rt);
}
void pushdown(int rt)
{
    if(add[rt]!=0)
    {
        sum[rt<<1|1]+=add[rt];
        sum[rt<<1]+=add[rt];
        add[rt<<1|1]+=add[rt];
        add[rt<<1]+=add[rt];
        add[rt]=0;
    }
}
void update(int L,int R,int c,int l,int r,int rt)
{
    if(L<=l&&R>=r)
    {
        sum[rt]+=c;
        add[rt]+=c;
        return ;
    }
    pushdown(rt);
    int m=(l+r)>>1;
    if(L<=m) update(L,R,c,l,m,rt<<1);
    if(R>m) update(L,R,c,m+1,r,rt<<1|1);
    pushup(rt);
}
double query(int L,int R,int l,int r,int rt)
{
    if(L<=l&&R>=r)
    {
        return sum[rt];
    }
    pushdown(rt);
    int m=(l+r)>>1;
    double ans=INF;
    if(L<=m) ans=min(ans,query(L,R,l,m,rt<<1));
    if(R>m) ans=min(ans,query(L,R,m+1,r,rt<<1|1));
    return ans;
}
bool solve(double mid)
{
    build(1,1,n,mid);
    for(int i=1;i<=n;i++)
    {
        update(pre[i]+1,i,1,1,n,1);
        if(query(1,i,1,n,1)<=1.0*mid*(i+1.0)) return 1;
    }
    return 0;
}
int main()
{
    int t,x;
    scanf("%d",&t);
    while(t--)
    {
        memset(last,-1,sizeof(last));
        memset(pre,0,sizeof(pre));
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&x);
            if(last[x]!=-1)
            pre[i]=last[x];
            last[x]=i;
        }
        double l=0,r=1.0,mid;
        while(r-l>eps)
        {
            mid=(l+r)/2;
            if(solve(mid)) r=mid-eps;
            else l=mid;
        }
        printf("%.6f\n",r);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值