题意:给你一个长度为n的数组,让你求size(l, r)/(r-l+1)的最小值。[l, r]是给定数组一个子序列,size(l, r)的值为这个区间的不同数的个数。(n <= 6e4)
官方题解:
二分答案mid,检验是否存在一个区间满足r−l+1size(l,r)≤mid,也就是size(l,r)+mid×l≤mid×(r+1)。
从左往右枚举每个位置作为r,当r变化为r+1时,对size的影响是一段区间加1,线段树维护区间最小值即可。 时间复杂度O(nlognlogw)。
现在要找的是size(l,r)+mid*l的最小值。在维护线段树的时候,一开始先是一颗空树,然后每次加入一个数(从左到右,位置从1~n)就查询一下。
为什么这样呢?试想一下当我插入一个数的时候会对哪些区间造成影响,也就是哪些区间之前是没有当前插入的这个数的。那应该是当前插入的这个
数与上一次插入这个数的时候的位置的这段区间(记住我充计的是区间的不同数的个数),那么就对这段区间+1。然后再查询,查询的是以 i 为右端点
的最小值是多少。为什么只枚举R不枚举L呢?这里可以结合代码看一下,在线段树中当确定了R之后就会从线段树的根节点开始一直往下查找,知道R
不满足了位置,在这个过程中其实是对所有的L已经遍历了的,所以只需要枚举R就行了(挺巧妙的)。因为每添加一个数就对以当前添加的这个数的
位置为R进行一次查询,所以不仅会对所有的区间查询还对后面的没有影响(后面的数还没添加)。因为有精度,所以二分的次数有限。
代码:
#include
using namespace std;
const int maxn = 1e5+5;
int pre[maxn], a[maxn], lazy[maxn*4], n;
double tree[maxn*4];
void push_up(int root)
{
tree[root] = min(tree[root*2], tree[root*2+1]);
}
void push_down(int root, int l, int r)
{
if(lazy[root])
{
int mid = (l+r)/2;
tree[root*2] += lazy[root];
tree[root*2+1] += lazy[root];
lazy[root*2] += lazy[root];
lazy[root*2+1] += lazy[root];
lazy[root] = 0;
}
}
void build(int root, int l, int r, double val)
{
lazy[root] = 0;
if(l == r)
{
tree[root] = val*l;
return;
}
int mid = (l+r)/2;
build(root*2, l, mid, val);
build(root*2+1, mid+1, r, val);
push_up(root);
}
void update(int root, int l, int r, int i, int j, int val)
{
if(i <= l && j >= r)
{
lazy[root] += val;
tree[root] += val;
return ;
}
push_down(root, l, r);
int mid = (l+r)/2;
if(i <= mid) update(root*2, l, mid, i, j, val);
if(j > mid) update(root*2+1, mid+1, r, i, j, val);
push_up(root);
}
double query(int root, int l, int r, int i, int j)
{
if(i <= l && j >= r)
return tree[root];
push_down(root, l, r);
int mid = (l+r)/2;
double ans = 1e9;
if(i <= mid) ans = min(ans, query(root*2, l, mid, i, j));
if(j > mid) ans = min(ans, query(root*2+1, mid+1, r, i, j));
return ans;
}
int main(void)
{
int _;
cin >> _;
while(_--)
{
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
double l = 0.0, r = 1.0;
double ans = 1.0;
for(int i = 0; i <= 20; i++)
{
double mid = (l+r)/2;
build(1, 1, n, mid);
memset(pre, 0, sizeof(pre));
bool ok = 0;
for(int i = 1; i <= n; i++)
{
update(1, 1, n, pre[a[i]]+1, i, 1);
pre[a[i]] = i;
double tmp = query(1, 1, n, 1, i);
if(tmp <= mid*(i+1))
{
ok = 1;
break;
}
}
if(ok) r = mid, ans = mid;
else l = mid;
}
printf("%.10f\n", ans);
}
return 0;
}