做多校的时候只想到用线段树维护sz【L,R】的值,一顿瞎想全都是复杂度爆表的。。。。。。。还是太弱。
废话不说,先看题意,令F【L,R】为L,R区间内数字种数/数字总个数,求最小的F【L,R】。
即最小的【L,R】/(R-L+1)。
mid>=sz【L,R】/(R-L+1),化为 sz【L,R】+L*mid<=(R+1)*mid,很明显的分数规划了。
二分答案,枚举左端点R,然后用线段树维护区间的sz【L,R】+L*mid的最小值。每加入一个R,更新【pre【R】+1,R】,pre【R】为上一次出现R的位置。若线段树一个节点的值为5,6,7,该节点维护【5,R】,【6,R】,【7,R】的sz【L,R】+L*mid最小值。复杂度为O(knlong),k为二分次数。
#include <bits/stdc++.h>
#define eps 0.00001
#define MAXN 60010
#define INF 999999999
using namespace std ;
double sum[MAXN<<2],add[MAXN<<2];
int n,last[MAXN],pre[MAXN];
void pushup(int rt)
{
sum[rt]=min(sum[rt<<1],sum[rt<<1|1]);
}
void build(int rt,int l,int r,double mid)
{
add[rt]=0;
if(l==r)
{
sum[rt]=1.0*mid*l;
return ;
}
int m=(l+r)>>1;
build(rt<<1,l,m,mid);
build(rt<<1|1,m+1,r,mid);
pushup(rt);
}
void pushdown(int rt)
{
if(add[rt]!=0)
{
sum[rt<<1|1]+=add[rt];
sum[rt<<1]+=add[rt];
add[rt<<1|1]+=add[rt];
add[rt<<1]+=add[rt];
add[rt]=0;
}
}
void update(int L,int R,int c,int l,int r,int rt)
{
if(L<=l&&R>=r)
{
sum[rt]+=c;
add[rt]+=c;
return ;
}
pushdown(rt);
int m=(l+r)>>1;
if(L<=m) update(L,R,c,l,m,rt<<1);
if(R>m) update(L,R,c,m+1,r,rt<<1|1);
pushup(rt);
}
double query(int L,int R,int l,int r,int rt)
{
if(L<=l&&R>=r)
{
return sum[rt];
}
pushdown(rt);
int m=(l+r)>>1;
double ans=INF;
if(L<=m) ans=min(ans,query(L,R,l,m,rt<<1));
if(R>m) ans=min(ans,query(L,R,m+1,r,rt<<1|1));
return ans;
}
bool solve(double mid)
{
build(1,1,n,mid);
for(int i=1;i<=n;i++)
{
update(pre[i]+1,i,1,1,n,1);
if(query(1,i,1,n,1)<=1.0*mid*(i+1.0)) return 1;
}
return 0;
}
int main()
{
int t,x;
scanf("%d",&t);
while(t--)
{
memset(last,-1,sizeof(last));
memset(pre,0,sizeof(pre));
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&x);
if(last[x]!=-1)
pre[i]=last[x];
last[x]=i;
}
double l=0,r=1.0,mid;
while(r-l>eps)
{
mid=(l+r)/2;
if(solve(mid)) r=mid-eps;
else l=mid;
}
printf("%.6f\n",r);
}
return 0;
}