题意:
给你一个数组,找一段区间使得区间内不同数的个数与区间长度的比值最小。
Solution:
这是一道经典的分数规划题,考虑二分答案k,那么我们的目标就是找一段区间使得val/len<=k,val是区间内不同的数的个数,len是区间长度,转化一下可以得到val-k*len<=0,我们可以利用线段树达到快速查询的效果:维护区间最小值,pre[a[i]]记录上一个值为a[i]的数出现的位置,每次把[pre[i]+1,i]这段区间+1,[1,i]这段区间-k,每次操作完后求[1,i]区间的最小值,如果小于0那么说明符合条件。
代码:
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int N=60010;
struct tree{
int l,r;
double v,tag;
}tr[4*N];
int n,T,a[N];
int pre[N];
const double eps=1e-6;
void update(int i)
{
tr[i].v=min(tr[i<<1].v,tr[i<<1|1].v);
}
void build(int i,int l,int r)
{
tr[i].l=l;tr[i].r=r;tr[i].v=0;tr[i].tag=0;
if (l==r) return;
int mid=l+r>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
}
void add(int i,double x)
{
tr[i].tag+=x;
tr[i].v+=x;
}
void pushdown(int i)
{
if (tr[i].tag)
{
if (tr[i].l==tr[i].r) {tr[i].tag=0;return;}
add(i<<1,tr[i].tag);
add(i<<1|1,tr[i].tag);
tr[i].tag=0;
}
}
void modify(int i,int l,int r,double v)
{
int L=tr[i].l,R=tr[i].r;
if (L>r||l>R) return;
if (l<=L&&R<=r) {add(i,v);return;}
pushdown(i);
modify(i<<1,l,r,v);
modify(i<<1|1,l,r,v);
update(i);
}
double query(int i,int l,int r)
{
int L=tr[i].l,R=tr[i].r;
if (L>r||l>R) return 1e9;
if (l<=L&&R<=r) return tr[i].v;
pushdown(i);
double ans=1e9;
ans=min(ans,query(i<<1,l,r));
ans=min(ans,query(i<<1|1,l,r));
return ans;
}
bool solve(double k)
{
build(1,1,n);
memset(pre,0,sizeof(pre));
for (int i=1;i<=n;i++)
{
modify(1,pre[a[i]]+1,i,1);
modify(1,1,i,-k);
if (query(1,1,i)<=0) return 1;
pre[a[i]]=i;
}
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,ans=1;
while (r-l>=eps)
{
double mid=(l+r)/2;
if (solve(mid)) ans=mid,r=mid;
else l=mid;
}
printf("%.5f\n",ans);
}
}