版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hfuu1504011020/article/details/76695127
题意:就是要我们求一个区间不同种类的个数与该区间的长度的比值,然后取比值最小值。
思想:比赛时想到用线段树去处理他,与平时写的线段树它的维护区间有很多差别,之后实在没法去维护就放弃了,之后看了题解,又看了一些博客,总算了解了它是如何维护区间值。首先我们可以二分答案最小比值,然后根据二分的答案乘以它的个数此时得到的就是种类数,然后我们对种类数进行维护,由于种类数受个数增加的影响,但每次移动我们加1,再然后我们就去更新这个区间的最小值,确定最小值后(即该区间的种类个数),判断最小值减去(二分时的答案乘以此时右移的个数)是否小于eps,剩余的就是二分的过程。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int MAX=6e4+10;
const double eps=1e-5;
#define INF 0x3f3f3f3f
int T;
int n;
int a[MAX<<2],q[MAX<<2],p[MAX<<2];
double sum[MAX<<2];
int vis[MAX<<2];
void pushup(int cur)
{
sum[cur]=min(sum[cur<<1],sum[cur<<1|1]);
}
void pushdown(int cur)
{
if(vis[cur])
{
sum[cur<<1]+=vis[cur];
sum[cur<<1|1]+=vis[cur];
vis[cur<<1]+=vis[cur];
vis[cur<<1|1]+=vis[cur];
vis[cur]=0;
return ;
}
}
void build(int cur,int l,int r,int id,double x)
{
if(l==r)
{
sum[cur]=l*x;
return ;
}
int mid=l+r>>1;
pushdown(cur);
if(id<=mid) build(cur<<1,l,mid,id,x);
else build(cur<<1|1,mid+1,r,id,x);
pushup(cur);
}
void updata(int cur,int L,int R,int l,int r)
{
if(L<=l&&R>=r)
{
sum[cur]+=1;
vis[cur]+=1;
return ;
}
int mid=l+r>>1;
pushdown(cur);
if(mid>=L) updata(cur<<1,L,R,l,mid);
if(mid<R) updata(cur<<1|1,L,R,mid+1,r);
pushup(cur);
}
int query(double x)
{
for(int i=1;i<MAX*4;i++) sum[i]=INF;
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++)
{
build(1,1,n,i,x);
updata(1,p[i]+1,i,1,n);
if(sum[1]-x*(i+1)<eps) //(二分的答案乘以右移的个数)与我们更新的种类数的比较。
return 1;
}
return 0;
}
int main()
{
while(~scanf("%d",&T))
{
while(T--)
{
scanf("%d",&n);
memset(q,0,sizeof(q));
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
p[i]=q[a[i]];
q[a[i]]=i;
}
double l=0,r=1,cnt=0;
while(r-l>eps)
{
double mid=(l+r)/2;
if(query(mid))
{
cnt=mid;
r=mid-eps ;
//cout<<cnt<<endl;
}
else l=mid+eps ;
//cout<<cnt<<endl;
}
printf("%.10f\n",cnt);
}
}
return 0;
}
题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=6070