hdu 6070 枚举答案+线段树

题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=6070


题意:给你长度为n的序列,求某个区间[l,r]使得区间内的数字种类/区间长度最小输出这个最小值


官方题解:

思路详解:

首先从答案入手,二分答案

每次二分的答案我们要找是否存在区间满足size(l,r)+mid*l<=mid*(r+1),如果存在的话继续往小的方向二分,如果不存在的话继续往大的方向二分

然后我们从左往右枚举r,每次就相当于插入一个新数,然后我们需要更新被这个数影响的那些区间的size值,然后线段树区间维护

size(l,r)+mid*l的最小值就可以了

tag数组:存的是每个区间的size值

v数组:存的是该区间中size(l,r)+mid*l的值


标程代码:

#include<cstdio>
const int N=60010,M=131100;
int Case,n,i,a[N],ap[N],tag[M];
double v[M],t,L,R,MID;
inline double min(double a,double b)
{
    return a<b?a:b;
}
inline void tag1(int x,int p)
{
    tag[x]+=p;
    v[x]+=p;
}
inline void pb(int x)
{
    if(tag[x])
    {
        tag1(x<<1,tag[x]);
        tag1(x<<1|1,tag[x]);
        tag[x]=0;
    }
}
void build(int x,int a,int b)
{
    v[x]=MID*a,tag[x]=0;//初始时size()为0,v[x]初始化为mid*l
    if(a==b)return;
    int mid=(a+b)>>1;
    build(x*2,a,mid),build(2*x+1,mid+1,b);
}
void change(int x,int a,int b,int c,int d)
{
    if(c<=a&&b<=d)
    {
        tag[x]+=1;
        v[x]+=1;
        return;
    }
    pb(x);//以[c,d]间的数为左端点,以d为右端点区间全是需要更新的区间
    int mid=(a+b)>>1;
    if(c<=mid)change(x<<1,a,mid,c,d);
    if(d>mid)change(x<<1|1,mid+1,b,c,d);
    v[x]=min(v[x<<1],v[x<<1|1]);
}
void ask(int x,int a,int b,int d)
{
    if(b<=d)//只要这个查询区间的右端点小于目前枚举的右端点即可记录答案
        //这样可以不必一一枚举区间的左端点
    {
        if(t>v[x])t=v[x];
        return;
    }
    pb(x);
    int mid=(a+b)>>1;
    ask(x<<1,a,mid,d);
    if(d>mid)ask(x<<1|1,mid+1,b,d);
}
int main()
{
    scanf("%d",&Case);
    while(Case--)
    {
        scanf("%d",&n);
        for(i=1; i<=n; i++)scanf("%d",&a[i]);
        L=0,R=1;
        for(int _=20; _; _--)//二分的次数,保证精度
        {
            MID=(L+R)/2;
            build(1,1,n);
            //ap[i]为i上一次出现的位置坐标
            for(i=1; i<=n; i++)ap[i]=0;
            for(i=1; i<=n; i++)
            {
                change(1,1,n,ap[a[i]]+1,i);//更新那些被插入的a[i]影响的区间
                t=1e9;
                ask(1,1,n,i);//查找所有以i为右端店的区间的size(l,r)+mid*l的最小值
                if(t-MID*(i+1)<=0)break;
                ap[a[i]]=i;
            }
            if(i<=n)R=MID;
            else L=MID;
        }
        printf("%.10f\n",(L+R)/2);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值