给一个 n 个数的序列,选出一段区间,使得这段区间 数字种数数字个数 的值最小,输出这个最小值。误差不要超过1e-4,1 <= n <= 60000,1 <= ai <= n
这道题看到之后应该能想出来是二分,这种题通常都需要二分。但是没有想出二分完怎么判断是否可行。
判断也就是判断是否有一个区间满足 size(l,r)r−l+1≤mid 。看见这个式子感觉还是没法很快判断是否有这个区间,因为 l 和 r 都不固定。答案很巧妙,将这个式子整理一下,变成 size(l,r)+mid×l≤mid×(r+1) ,在这个式子中,将 mid 看作是个定值是关键。现在我们枚举区间两个点肯定不允许,所以就想只枚举一个。枚举右端点,那么右端点 r 也是个定值。要想让不等式成立,我们只需让式子左边的最小值小于等于右边就可以了。
所以问题就变成了右端点 r 固定的情况下,如何求左边最小值。想到用线段树维护 size(l,r)+mid×l 。查询的时候查询 [1,r] 这一截的最小值即可,每次查询左端点不变。更新的话,区间右端点是当前这个数字,左端点是当前这个数字上次出现的位置的后一个。将这一截打上标记即可。
反思。完全没想到可以多次建树,思维还是很固定。而且那个不等式想不到去整理转化。因为是不等式,并非等式,所以确定一个区间不一定要两个端点,固定一个找最值也可以。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define ms(a,b) memset(a,b,sizeof(a))
#define lson rt*2,l,(l+r)/2
#define rson rt*2+1,(l+r)/2+1,r
typedef long long ll;
const int MAXN = 6e4+5;
const double EPS = 1e-5;
const int INF = 0x3f3f3f3f;
int pre[MAXN],a[MAXN],tag[MAXN << 2],n;
double v[MAXN << 2];
void pushup(int rt){
v[rt] = min(v[rt<<1],v[rt<<1 | 1]);
}
void putTag(int rt,int x){
tag[rt] += x;
v[rt] += x;
}
void pushdown(int rt){
if(tag[rt]){
putTag(rt << 1,tag[rt]);
putTag(rt << 1 | 1,tag[rt]);
tag[rt] = 0;
}
}
void build(int rt,int l ,int r,double mid){
v[rt] = mid * l;
tag[rt] = 0;
if(l==r) return;
build(lson,mid);
build(rson,mid);
pushup(rt);
}
void update(int L,int R,int rt,int l,int r){
if(L <= l && R >= r){
putTag(rt,1);
return;
}
pushdown(rt);
if(L <= (l+r)/2) update(L,R,lson);
if(R > (l+r)/2) update(L,R,rson);
pushup(rt);
return;
}
double query(int R,int rt,int l,int r){
if(R >= r){
return v[rt];
}
pushdown(rt);
if(R <= (l+r)/2) return query(R,lson);
return min(query(R,lson),query(R,rson));
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
double l = 0.0,r = 1.0,ans;
while(abs(l-r) >= EPS){
double mid = (l+r)/2;
build(1,1,n,mid);
ms(pre,0);
int i;
for(i=1;i<=n;i++){
update(pre[a[i]]+1,i,1,1,n);
if(query(i,1,1,n) <= mid*(i+1)) break;
pre[a[i]] = i;
}
if(i<=n){
r = mid - 0.00001;
ans = mid;
}else{
l = mid + 0.00001;
}
}
printf("%f\n", ans);
}
return 0;
}