- 题目大意
给定一个序列a[],让你求出一个子区间内不同元素个数除以区间长度的最小的一个值。 n≤6∗104 ,答案精度不高于 10−4
分析
题目要求的是:
min{dif[l][r]r−l+1}(1≤l≤r≤n,dif[l][r]表示区间[l,r]内不同元素的个数)
最暴力的方法通过两层循环来枚举这个区间,外层循环是左端点,内层循环是右端点。
类似于CF 833B,我们可以用一个数组 pre[x] 保存在 x 前面最近的和a[x] 相等的数的下标,这样我们可以通过 O(1) 的复杂度从 dif[l][r] 推得 dif[l][r+1]
同样类似于CF 833B,这样的 O(n2) 的复杂度还是会超时
我们尝试去用一种数据结构来维护这个区间最小值
我们可以 O(1) 的复杂度从 dif[l][r] 推得 dif[l][r+1] ,那么我们可以通过线段树以 O(logn) 的复杂度从
dif[1...l][r] 推得 dif[1...l][r+1] ,但是这个区间信息是一个带除法运算的表达式我们很难对这个分母进行区间的更新操作
如果我们能够将除法运算转化成加法运算。
。
如果我们想到了二分, min{dif[l][r]r−l+1}≤mid 是否成立
变换一下也就是 min{dif[l][r]+l∗mid−mid}≤r∗mid 是否成立
这样我们就能够对表达式 dif[l][r]+l∗mid−mid 通过线段树来进行区间更新和查询操作了代码
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<map>
#include<algorithm>
#include<set>
#include<stack>
using namespace std;
const double INF=999999.0;
const long long int MOD=998244353;
const int MAXN=60005;
int T;
int n;
int a[MAXN];
int pre[MAXN];
int pos[MAXN];
double tree[MAXN*4];
double lazy[MAXN*4];
void PushUp(int rt)//将延迟标记上推
{
tree[rt]=min(tree[rt*2],tree[rt*2+1]);
}
void PushDown(int rt)
{
lazy[rt*2]+=lazy[rt];
lazy[rt*2+1]+=lazy[rt];
tree[rt*2]+=lazy[rt];
tree[rt*2+1]+=lazy[rt];
lazy[rt]=0;
return ;
}
void Build(double x,int l,int r,int rt)
{
lazy[rt]=0;
if(l==r)
{
tree[rt]=(double)l*x-x;
return ;
}
int m=(l+r)/2;
Build(x,l,m,rt*2);
Build(x,m+1,r,rt*2+1);
PushUp(rt);
}
void Update(int L,int R,int l,int r,int rt)
{
if(l>=L && r<=R)
{
lazy[rt]+=1;
tree[rt]+=1;
return ;
}
int m=(l+r)/2;
PushDown(rt);
if(L<=m)Update(L,R,l,m,rt*2);
if(R>m)Update(L,R,m+1,r,rt*2+1);
PushUp(rt);
}
double Query(int L,int R,int l,int r,int rt)
{
if(l>=L &&r<=R)
{
return tree[rt];
}
double ans=INF;
PushDown(rt);
int m=(l+r)/2;
if(L<=m)ans=min(ans,Query(L,R,l,m,rt*2));
if(R>m)ans=min(ans,Query(L,R,m+1,r,rt*2+1));
PushUp(rt);
return ans;
}
bool Check(double x)//判断x作为答案是否可行
{
Build(x,1,n,1);
double minm=INF;
for(int i=1;i<=n;i++)
{
Update(pre[i]+1,i,1,n,1);
if(Query(1,i,1,n,1)<=(double)i*x){return 1;}
}
return 0;
}
void Work()
{
double l=0;
double r=1.0;
double m;
while(r-l>0.0000001)
{
m=(l+r)/2;
if(Check(m))r=m;
else l=m;
}
cout<<l<<endl;
}
int main()
{
scanf("%d",&T);
while(T--)
{
memset(pos,0,sizeof(pos));
memset(pre,0,sizeof(pre));
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
pre[i]=pos[a[i]];
pos[a[i]]=i;
}
Work();
}
return 0;
}
/*
1
5
1 2 1 2 3
*/