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]\)满足

\[\frac{num[l:r]}{r-l+1} \leqslant mid \]

直接求的话,即使\(num[l:r]\)可以\(O(1)\)得到,也要枚举全部的\(O(n^2)\)个区间,时间不允许。另一方面,如果可以计算每个区间的值,就不需要二分了。

可以考虑每次向右推进右端点,线段树维护前面每个端点到右端点的值\(num[l:r]/len\),但此时维护的是一个浮点数,更新的时候没法更新,如果维护分子分母两个值,又没法查询比值的最小值。

这时的解决方案是化分式为整式,把分母乘过去,将原式变为

\[num[l:r] + mid \cdot l \leqslant mid \cdot (r+1) \]

建立线段树,线段树的每个结点\(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;
}

http://acm.hdu.edu.cn/showproblem.php?pid=6070

posted @ 2017-08-06 14:49  达达Mr_X  阅读(173)  评论(0编辑  收藏  举报