题目大意:
让你找到一个区间:【L,R】,使得其中的数字的种类数/区间长度最小。
问这个比例最小是多少。
思路:
考虑分数规划:
①我们希望Val【L,R】/(R-L+1)最小。这里Val【L,R】表示区间中数字的种类数。
②我们不妨设定F(X)=Val【L,R】-X*(R-L+1);
③那么如果有F(X)<=0,那么一定有Val【L,R】/(R-L+1)<=X;
④所以我们这个最小的值可以通过二分来求出来。
那么我们如何check呢?
我们再转化一下不等式变成:Val【L,R】+mid*L<=(R+1)*mid.(这里X==mid);
那么我们O(n)枚举R,之前我们可以利用线段树来维护mid*L+Val【L,R】的最小值,那么如果查询Query(1,i-1)能够<=(R+1)*mid的话,就说明可以继续向下二分。
否则向上。
我们线段树更新mid*L很简单,一路update(i,i,mid*i)即可,那么Val【L,R】怎样处理呢?其实也不难,我们维护一个数组last【i】,表示数字i出现的最后的位子,那么对于当前数i,其影响的区间Val的值的范围就是从【last【a【i】】+1,i】,那么再update一下(last【a【i】】+1,i,1)即可。
尽量去for二分。
Ac代码:
#include<stdio.h> #include<iostream> #include<string.h> using namespace std; #define lson l,m,rt*2 #define rson m+1,r,rt*2+1 double tree[350050*8]; double flag[350050*8]; int last[350050]; int a[350050]; void build(int l,int r,int rt){ flag[rt]=0; if(l==r) { tree[rt]=0; flag[rt]=0; return; } int m=(l+r)>>1; build(lson); build(rson); } void pushdown(int l,int r,int rt)//向下维护树内数据 { if(flag[rt])//如果贪婪标记不是0(说明需要向下进行覆盖区间(或点)的值) { int m=(l+r)/2; flag[rt*2]+=flag[rt]; flag[rt*2+1]+=flag[rt]; tree[rt*2]+=flag[rt];//千万理解如何覆盖的区间值(对应线段树的图片理解(m-l)+1)是什么意识. tree[rt*2+1]+=flag[rt]; flag[rt]=0; } } void pushup(int rt) { tree[rt]=max(tree[rt<<1],tree[rt<<1|1]); } double Query(int L,int R,int l,int r,int rt) { if(L<=l&&r<=R) { return tree[rt]; } else { pushdown(l,r,rt); int m=(l+r)>>1; double ans=-10000000000000; if(L<=m) { ans=max(ans,Query(L,R,lson)); } if(m<R) { ans=max(ans,Query(L,R,rson)); } pushup(rt); return ans; } } void update(int L,int R,double c,int l,int r,int rt) { if(L<=l&&r<=R)//覆盖的是区间~ { tree[rt]+=c;//覆盖当前点的值 flag[rt]+=c;//同时懒惰标记~! return ; } pushdown(l,r,rt); int m=(l+r)/2; if(L<=m) { update(L,R,c,lson); } if(m<R) { update(L,R,c,rson); } pushup(rt); } int Slove(int n,double mid) { build(1,n,1); for(int i=1;i<=n;i++)last[a[i]]=0; for(int i=1;i<=n;i++) { update(i,i,-mid*i,1,n,1); update(last[a[i]]+1,i,-1,1,n,1); last[a[i]]=i; if(i==1)continue; double Q=-Query(1,i-1,1,n,1); if(Q<=(i+1)*mid)return 1; } return 0; } int main() { int t; scanf("%d",&t); while(t--) { int n; scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%d",&a[i]); double l=0; double r=1; double ans; for(int i=0;i<20;i++) { double mid=(l+r)/2; if(Slove(n,mid)==1) { ans=mid; r=mid; } else l=mid; } printf("%.10f\n",ans); } }