这个题是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;
}
}
看一看updatevoid 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;
}
到了这也差不多了,对于询问,也就是找到最小值返回即可