题面
传送门
此题的题意不是很清晰,要注意的一点是在区间[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)为区间中不同元素的个数,求值最大的区间,输出其最大值
分析
1.二分答案
很明显此题用直接枚举区间的方法会超时,必须使用二分来降低时间复杂度
如果二分区间端点,则答案不存在单调性,无法二分
因此二分答案,显然答案可取的范围是[0,1)
设二分中点为mid,由题意 ([l,r]为所有区间中值最小的区间)
两边同乘化简r-l+1,移项,整理得
2.求不等式左边的值,判断mid是否可取到
对于每次二分,枚举r从1~n,对于每一个r,我们再求右端点为r的所有区间的所有区间的值中的最小值
我们维护一棵线段树,线段数的每个叶子节点l表示左端点为l,右端点为r(当前的枚举值)时的值,再统计区间[1,r]最小值
这样我们就求出了右端点为r的所有区间的值中的最小值
由于r从1~n,线段树中的值也在不断变化,我们怎么更新线段树中的值呢?
每次r+1,需要更新[r,r]这个区间,区间加,
再更新[x,r]各区间的cnt值,设a上一次出现的位置为last[a],则区间[last[a]+1,r]都需要+1,因为区间内出现了一个新的数a,而对于last[a]之前的位置,由于a已经出现过,cnt值不受影响
时间复杂度分析:
二分时间复杂度为,其中 为精度,在此题中设即可
线段树的建树时间复杂度为,n次插入与查询时间复杂度为
所以总的时间复杂度分析为
代码:
#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);
}
}