• HDU 6070题解(二分+线段树)


    题面

    传送门
    此题的题意不是很清晰,要注意的一点是在区间[L,R]中,默认题目编号最后一次出现的时候是AC的
    比如1 2 1 2 3 ,在区间[1,4]中,第3次提交时AC第1题,第4次提交时AC第2题,故比例为2/4=0.5
    所以此问题可以转化为
    给定一个序列,定义区间[l,r]的值为cnt(l,r)rl+1,(cnt(l,r)为区间中不同元素的个数,求值最大的区间,输出其最大值

    分析

    1.二分答案

    很明显此题用直接枚举区间的方法会超时,必须使用二分来降低时间复杂度
    如果二分区间端点,则答案不存在单调性,无法二分
    因此二分答案,显然答案可取的范围是[0,1)
    设二分中点为mid,由题意cnt(l,r)rl+1mid ([l,r]为所有区间中值最小的区间)
    两边同乘化简r-l+1,移项,整理得cnt(l,r)+mid×lmid×(r+1)

    2.求不等式左边的值,判断mid是否可取到

    对于每次二分,枚举r从1~n,对于每一个r,我们再求右端点为r的所有区间的所有区间的值中的最小值
    我们维护一棵线段树,线段数的每个叶子节点l表示左端点为l,右端点为r(当前的枚举值)时的值,再统计区间[1,r]最小值
    这样我们就求出了右端点为r的所有区间的值中的最小值
    由于r从1~n,线段树中的值也在不断变化,我们怎么更新线段树中的值呢?
    每次r+1,需要更新[r,r]这个区间,区间加mid×r,
    再更新[x,r]各区间的cnt值,设a上一次出现的位置为last[a],则区间[last[a]+1,r]都需要+1,因为区间内出现了一个新的数a,而对于last[a]之前的位置,由于a已经出现过,cnt值不受影响

    时间复杂度分析:
    二分时间复杂度为O(log21ϵ),其中 ϵ为精度,在此题中设ϵ=105即可
    线段树的建树时间复杂度为O(nlog2n),n次插入与查询时间复杂度为O(nlog2n)
    所以总的时间复杂度分析为O(nlog2n×log21ϵ)

    代码:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #define maxn 60005
    #define eps 1e-5
    #define INF 0x7fffffff
    using namespace std;
    int a[maxn];
    int pos[maxn],last[maxn];
    struct node{
        int l;
        int r;
        double mark;
        double v;
        int len(){
            return r-l+1;
        }
    }tree[maxn<<2];
    void push_up(int pos){
        tree[pos].v=min(tree[pos<<1].v,tree[pos<<1|1].v);
    }
    void build(int l,int r,int pos){
        tree[pos].l=l;
        tree[pos].r=r;
        tree[pos].v=0;
        tree[pos].mark=0;
        if(l==r) return;
        int mid=(l+r)>>1;
        build(l,mid,pos<<1);
        build(mid+1,r,pos<<1|1);
    } 
    void push_down(int pos){
        if(tree[pos].mark!=0){
            tree[pos<<1].v+=tree[pos].mark;
            tree[pos<<1|1].v+=tree[pos].mark;
            tree[pos<<1].mark+=tree[pos].mark;
            tree[pos<<1|1].mark+=tree[pos].mark;
            tree[pos].mark=0;
        }
    }
    void update(int L,int R,double v,int pos){
        int l=tree[pos].l,r=tree[pos].r;
        if(L<=l&&R>=r){
            tree[pos].v+=v;
            tree[pos].mark+=v;
            return;
        } 
        push_down(pos); 
        int mid=(l+r)>>1;
        if(L<=mid) update(L,R,v,pos<<1);
        if(R>mid) update(L,R,v,pos<<1|1);
        push_up(pos);
    }
    double query(int L,int R,int pos){
        int l=tree[pos].l,r=tree[pos].r;
        if(L<=l&&R>=r){
            return tree[pos].v;
        }
        push_down(pos);
        int mid=(l+r)>>1;
        double ans=INF;
        if(L<=mid) ans=min(ans,query(L,R,pos<<1));
        if(R>mid) ans=min(ans,query(L,R,pos<<1|1));
        return ans; 
    }
    int t,n;
    int main(){
        scanf("%d",&t);
        while(t--){
            memset(last,0,sizeof(last));
            memset(pos,0,sizeof(pos));
            scanf("%d",&n);
            for(int i=1;i<=n;i++){
                scanf("%d",&a[i]);
            }
            for(int i=1;i<=n;i++){
                last[i]=pos[a[i]];
                pos[a[i]]=i;
            }
            double l=0,r=1;
            double ans=INF;
            while(fabs(l-r)>eps){
                build(1,n,1);
                double mid=(l+r)/2;
                bool is_min=false;
                for(int i=1;i<=n;i++){
                    update(i,i,mid*i,1);
                    update(last[i]+1,i,1,1);
                    double t=query(1,i,1);
                    if(mid*(i+1)>=t){
                        is_min=true;
                        break;
                    }
                }
                if(is_min){
                    r=mid;
                    ans=min(ans,mid);
                } 
                else l=mid;
            }
            printf("%lf
    ",ans);
        }
    } 
  • 相关阅读:
    [转]创建节约内存的JavaBean
    TCP协议
    redis常用命令
    Windows&Linux常用命令笔记
    二叉树学习笔记
    云计算入门
    redis主从切换
    spring 异步处理request
    ibatis工作原理
    工作了3年的JAVA程序员应该具备什么技能?(转)
  • 原文地址:https://www.cnblogs.com/birchtree/p/9845836.html
Copyright © 2020-2023  润新知