线段树——二分AC率的学习

本文详细解析了一道使用线段树与二分法结合解决的问题,通过实例展示了如何利用这两种算法求解特定区间内的最优化问题。文章深入浅出地介绍了线段树的构建、更新和查询过程,并结合二分法逼近最优解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >




这个题是HDU6070

本来以为线段树不过如此啊,就那几个模板函数,但是看了这个题不仅没和线段树联系起来,简直是一点思路都没有啊,看了很多博客讲解也没看到,就自己一直再里面打表进行理解,理解后,自己又写了一遍,A了

现在觉得针对区间的操作,能和区间沾点边的都是线段树~~~

题意很简单,让你求【i,j】 / len【i,j】的最小值,就是让你求区间i,j内不重复元素的个数 / 区间i,j的长度的最小值

以前对二分的理解也很片面,其实还是见的少,这个题允许10-4次方的误差,可以用二分法无限的接近(因为这个值肯定再0,1之间,所以就在0,1之间无限二分(有限,30次就odk了))
取最后的值为mid 则原式可以化简为——mid * l + [i,j] = mid * (j + 1)
所以遍历右区间即可

  for(int i=0; i<30; i++)//二分30次!
        {
            mid=(L+R)/2.0;
            if(check(n,mid))
                R=ans=mid;
            else
                L=ans=mid;
        }

根据差值的大小,来缩小区间

bool check(int n,double temp)
{
    printf("正在构建树…………\n\n");
    build(1,n,1,temp);//更新一波
    memset(pre,0,sizeof(pre));//更新一波
    for(int i=1; i<=n; i++)//遍历每一个值得区间
    {
        printf("准备更新维护…………\n\n");
        updata(pre[a[i]]+1,i,1,1,n,1);
        double minimum=query(1,i,1,n,1);//左边是固定的!!//最小值只会随着询问越来越小
        pre[a[i]]=i;
        if(temp*(i+1)-minimum>=eps)
            return true;
    }
    return false;
}

出现了pre【】数组,他的作用是去重,因为只要这个更新区间里不重复的元素,所以pre【a[i]】记录每一个元素上一次出现的位置,一开始更新为0(a[i] + 1 <= i)

先来看看建树的初始化过程

void build(int left,int right,int root,double temp)
{
    Min[root]=left*temp;
    lazy[root]=0;
    printf("left = %d right = %d root = %d temp = %lf\n",left,right,root,temp);
    if(left==right)
        return;
    int mid=(left+right)>>1;
    build(lchild,temp);
//    printf("left = %d right = %d root = %d temp = %lf\n",left,mid,root << 1,temp);
    build(rchild,temp);
//    printf("left = %d right = %d root = %d temp = %lf\n",mid + 1,right,root << 1 | 1,temp);
    push_up(root);
}
先对树的节点进行更新——一开始在节点内存值mid * l 并且把延迟标记更新为0,处理到叶子节点的时候返回
最后别放了向上更新父节点,父节点仅仅是存其区间最小值

pushup和pushdowm还是经典的那个

void push_up(int root)
{
    Min[root] = min(Min[root<<1],Min[root<<1|1]);
    printf("正在更新节点值\n\nMin(%d) = %lf\n左儿子Min(%d) = %lf  右儿子 = Min(%d) = %lf\n",root,Min[root],root<<1,Min[root<<1],root<<1|1,Min[root<<1|1]);
}
//懒惰标记下推
void push_down(int root)
{
    if(lazy[root]>eps)
    {
        printf("向下更新lazy标记\n");
        printf("自行更新————》root = %d lazy = %lf\n",root,lazy[root]);
        Min[root<<1] += lazy[root];
        lazy[root<<1] += lazy[root];
        Min[root<<1|1] += lazy[root];
        lazy[root<<1|1] += lazy[root];
        lazy[root]=0;
    }
}
看一看update

void updata(int L,int R,int add,int left,int right,int root)
{
    printf("查询区间【%d : %d】,线段树区间【%d : %d】,lazy(add)值 = %d,根节点 = %d\n",L,R,left,right,add,root);
    if(L<=left&&right<=R)
    {
    printf("\n找到最小包容区间Min[%d] + %d = %lf\n更新lazy标记 lazy[%d] + %d= %lf\n",root,add,Min[root] + add,root,add,lazy[root] + add);
      Min[root]+=add;      lazy[root]+=add;
      return;

    }
    push_down(root);
    int mid=(left+right)>>1;
    if(L<=mid)
        updata(L,R,add,lchild);
    if(R>mid)
        updata(L,R,add,rchild);
    push_up(root);
}

因为pre数组每一次update都包含了一个“新的”元素,所以都要进行lazy标记与更新 + 1
找到其最小包容的区间进行更新(这里有些不一样了)
因为我们主要是寻找每一个子区间的最小值,没必要涉及到任意一个区间,所以我们只需要维护二叉出来的子区间即可


double query(int L,int R,int left,int right,int root)
{
    printf("查询区间【%d : %d】,线段树区间【%d : %d】,根节点 = %d\n",L,R,left,right,root);
    if(L<=left&&right<=R)
        return Min[root];
    push_down(root);
    double ans=inf*1.0;
    int mid=(left+right)>>1;
    if(L<=mid)
        ans=min(ans,query(L,R,lchild));
    if(R>mid)
        ans=min(ans,query(L,R,rchild));
    printf("找到当前最小值————————%lf\n[root] = %d\n",ans,root);
    return ans;
}
到了这也差不多了,对于询问,也就是找到最小值返回即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值