题意: 给出的题目id表示几次AC,询问区间中最少的通过率
思路:
比赛时,不会枚举区间长度,题解的标程里写的很明白,涨姿势。。
Siz(l,r) 区间内的元素个数/ 区间长度 <=二分的答案,就表示该答案可以。
但是元素个数的求法很有意思。
在离线线段树时,可以解决区间内不同元素个数的问题。这次的想法感觉和离线的差不多,都是边更新,边查询。
每一次插入后,都会更新一段。而查找答案时,则是每次都必定会查找所有的前缀的最小值。
注意此时线段树每次建立,维护的是 siz(l,r) + mid * l
#include <algorithm>
#include <stdio.h>
#include <iostream>
using namespace std;
const int maxn=60010;
struct node
{
int l,r;
double tag;
double num;
}tree[maxn*4];
double MID;
int a[maxn];
int has[maxn];
double f;
void push_up(int i)
{
tree[i].num=min(tree[i<<1].num,tree[i<<1|1].num);
}
void push_down(int i)
{
if(tree[i].tag)
{
tree[i<<1].tag+=tree[i].tag;
tree[i<<1|1].tag+=tree[i].tag;
tree[i<<1].num+=tree[i].tag;
tree[i<<1|1].num+=tree[i].tag;
tree[i].tag=0;
}
}
void build(int i,int l,int r)
{
tree[i].l=l,tree[i].r=r;
tree[i].num=MID*l;
tree[i].tag=0;
if(l==r)
return ;
int mid=(l+r)>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
push_up(i);
}
void update(int i ,int nl,int nr)
{
if(nl<=tree[i].l&&tree[i].r<=nr)
{
tree[i].tag+=1;
tree[i].num+=1;
return ;
}
push_down(i);
int mid=(tree[i].l+tree[i].r)>>1;
if(nl<=mid)
update(i<<1,nl,nr);
if(nr>mid)
update(i<<1|1,nl,nr);
push_up(i);
}
void ask(int i,int pos)
{
if(tree[i].r<=pos)
{
if(f>tree[i].num)
f=tree[i].num;
return ;
}
push_down(i);
int mid=(tree[i].l+tree[i].r)>>1;
ask(i<<1,pos);
if(pos>mid)
ask(i<<1|1,pos);
push_up(i);
}
int main()
{
int t=0;
scanf("%d",&t);
int n;
for(int cs=1;cs<=t;cs++)
{
scanf("%d",&n);
for(int i=1; i<=n; i++)
scanf("%d",&a[i]);
double l=0,r=1;
for(int o=20; o; o--)
{
MID=(l+r)/2;
build(1,1,n);
for(int i=1; i<=n; i++)
has[i]=0;
int j ;
for( j=1; j<=n; j++)
{
update(1,has[a[j]]+1,j);
f=0x3f3f3f3f;
ask(1,j);
if(f-MID*(j+1)<=0)
{
break;
}
has[a[j]]=j;
}
if(j<=n)r=MID;
else l=MID;
}
printf("%.10f\n",(l+r)/2);
}
return 0;
}