题意: 区间价值为 区间元素种类数 / 区间长度 问最小价值的区间是?
思路:直接求解很困难,考虑二分答案判断,注意这题的关键是将二分答案后的不等式进行变换,如官方题解。二分答案 mid,检验是否存在一个区间满足 size(l,r)/(r−l+1) ≤ mid,也就是 size(l, r) + mid × l ≤mid × (r + 1)。
之后的问题就很好解决了,枚举右端点,开一颗线段树来维护到当前节点的区间不同数(类似树状数组求区间不同数的想法来加入一个新点,主要是通过一个数组维护上一次出现的位置)+ mid × l ,这样就可以轻松判断是否存在解了。
代码:
#include<bits/stdc++.h>
using namespace std;
#define MEM(a,b) memset(a,b,sizeof(a))
#define bug puts("bug");
inline int ll(int k) {return 2*k;}
inline int rr(int k) {return 2*k+1;}
inline int mid(int kk1,int kk2) {return (kk1+kk2)>>1;}
const int maxn=3e6+10;
struct pr {
double sum,lazy;
int left,right;
}tr[maxn+10];
void pushdown(int k) {
if(tr[k].lazy>1e-9){
tr[ll(k)].sum+=tr[k].lazy;tr[rr(k)].sum+=tr[k].lazy;
tr[ll(k)].lazy+=tr[k].lazy;
tr[rr(k)].lazy+=tr[k].lazy;
tr[k].lazy=0;
}
}
void build(int k,int s,int t,double MI) {
tr[k].left=s;tr[k].right=t;
if(s==t) {tr[k].sum=t*MI;return;}
build(ll(k),s,mid(s,t),MI);
build(rr(k),mid(s,t)+1,t,MI);
tr[k].sum=min(tr[ll(k)].sum,tr[rr(k)].sum);
tr[k].lazy=0;
}
void modify(int k,int s,int t,int x) {
int l=tr[k].left,r=tr[k].right;
if(l==s&&r==t) {
tr[k].lazy+=x,tr[k].sum+=x;
return ;
}
pushdown(k);
int mi=mid(l,r);
if(t<=mi) modify(ll(k),s,t,x);
else if(s>mi) modify(rr(k),s,t,x);
else modify(ll(k),s,mi,x),modify(rr(k),mi+1,t,x);
tr[k].sum=min(tr[ll(k)].sum,tr[rr(k)].sum);
}
double query(int k,int s,int t) {
int l=tr[k].left,r=tr[k].right;
if(l==s&&r==t) return tr[k].sum;
pushdown(k);
int mi=mid(l,r);
double res=1e20;
if (t<=mi) res=query(ll(k),s,t);
else if(s>mi) res=query(rr(k),s,t);
else res=min(query(ll(k),s,mi),query(rr(k),mi+1,t));
return res;
}
int a[700000],last[700000],t,n;
int cal(double x){
build(1,1,n,x);
MEM(last,0);
for(int i=1;i<=n;i++){
modify(1,last[a[i]]+1,i,1);
last[a[i]]=i;
if(query(1,1,i)<=x*(i+1)) return 1;
}
return 0;
}
int main(){
scanf("%d",&t);
while(t--){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
double L=0,R=1.0,mi;
for(int i=0;i<25;i++){
mi=(L+R)/2;
if(cal(mi)) R=mi;
else L=mi;
}
printf("%.8f
",R);
}
return 0;
}