HDU 6070 Dirt Ratio (二分+线段树)
Description
给出一个长度为\(n\)的序列,对于每一段连续区间\([l,r]\),定义\(num[l:r]\)为区间中不同的数字个数,区间长度为\(len\),求\(num[l:r]/len\)的最小值。
Input
第一行给出用例组数\(T\),对于每组用例,一行给出序列长度\(n\),第二行给出n个数表示这个序列。\(1 \leqslant n \leqslant 60000\),\(1 \leqslant a_i \leqslant n\)。
Output
对于每组用例,给出一个浮点数,表示\(num[l:r]/len\)的最小值。误差小于\(10^{-4}\)都算正确。
Sample Input
1
5
1 2 1 2 3
Sample Output
0.5000000000
Solution
要最小化一个值,考虑二分。对于二分得到的\(mid\),要检查是否存在区间\([l,r]\)满足
直接求的话,即使\(num[l:r]\)可以\(O(1)\)得到,也要枚举全部的\(O(n^2)\)个区间,时间不允许。另一方面,如果可以计算每个区间的值,就不需要二分了。
可以考虑每次向右推进右端点,线段树维护前面每个端点到右端点的值\(num[l:r]/len\),但此时维护的是一个浮点数,更新的时候没法更新,如果维护分子分母两个值,又没法查询比值的最小值。
这时的解决方案是化分式为整式,把分母乘过去,将原式变为
建立线段树,线段树的每个结点\(a[l]=num[l:r] + mid \cdot l\),\(mid \cdot l\)是确定值,可以提前存入,预处理出一个数组\(pre[]\),\(pre[i]\)表示序列中第\(i\)个数上一次出现的位置,这样右端点\(r\)每向右推进一位,只需更新\([pre[r]+1,r]\)区间。
检查每个答案的时间是\(O(nlogn)\),设二分次数为\(k\),则整体时间复杂度为\(O(knlogn)\)。这道题精度为\(10^{-4}\),大约二分\(20\)次即可达到精度要求。
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 6e4 + 10;
struct Node
{
int l, r;
double min;
int lazy;
} tree[4 * N];
int fa[N];
double a[N];
inline int L(int i) { return i << 1; }
inline int R(int i) { return (i << 1) + 1; }
inline int P(int i) { return i >> 1; }
void pushup(int i)
{
tree[i].min = min(tree[L(i)].min, tree[R(i)].min);
}
void build(int i, int left, int right)
{
tree[i].l = left; tree[i].r = right;
tree[i].lazy = 0;
if (left == right)
{
tree[i].min = a[left];
fa[left] = i;
return;
}
int mid = left + (right - left >> 1);
build(L(i), left, mid);
build(R(i), mid + 1, right);
pushup(i);
}
void pushdown(int i)
{
if (!tree[i].lazy) return;
tree[L(i)].min += tree[i].lazy;
tree[L(i)].lazy += tree[i].lazy;
tree[R(i)].min += tree[i].lazy;
tree[R(i)].lazy += tree[i].lazy;
tree[i].lazy = 0;
}
void update(int i, int left, int right, int val)
{
if (left <= tree[i].l && right >= tree[i].r)
{
tree[i].min += val;
tree[i].lazy += val;
if (tree[i].l == tree[i].r) a[tree[i].l] += val;
return;
}
pushdown(i);
int mid = tree[i].l + (tree[i].r - tree[i].l >> 1);
if (left <= mid) update(L(i), left, right, val);
if (right > mid) update(R(i), left, right, val);
pushup(i);
}
double query_min(int i, int left, int right)
{
if (left <= tree[i].l && right >= tree[i].r) return tree[i].min;
pushdown(i);
double minx = INF;
int mid = tree[i].l + (tree[i].r - tree[i].l >> 1);
if (left <= mid) minx = min(minx, query_min(L(i), left, right));
if (right > mid) minx = min(minx, query_min(R(i), left, right));
return minx;
}
int b[N], pre[N], pos[N], n;
bool check(double m)
{
for (int i = 1; i <= n; i++) a[i] = m * i;
build(1, 1, n);
for (int i = 1; i <= n; i++)
{
update(1, pre[i] + 1, i, 1);
double minx = query_min(1, 1, i);
if (minx <= m * (i + 1)) return true;
}
return false;
}
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", b + i);
memset(pos, 0, sizeof(pos));
for (int i = 1; i <= n; i++) pre[i] = pos[b[i]], pos[b[i]] = i;
double l = 0, r = 1;
for (int i = 1; i <= 20; i++)
{
double m = (l + r) / 2;
if (check(m)) r = m;
else l = m;
}
printf("%.5f\n", r);
}
return 0;
}