HDU 6070 Dirt Ratio(二分+线段树)

题意:给你一个长度为n的数组,让你求size(l, r)/(r-l+1)的最小值。[l, r]是给定数组一个子序列,size(l, r)的值为这个区间的不同数的个数。(n <= 6e4)


官方题解:

二分答案midmid,检验是否存在一个区间满足\frac{size(l,r)}{r-l+1}\leq midrl+1size(l,r)mid,也就是size(l,r)+mid×lmid×(r+1)

从左往右枚举每个位置作为rr,当rr变化为r+1r+1时,对sizesize的影响是一段区间加11,线段树维护区间最小值即可。 时间复杂度O(n\log n\log w)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;
}


你可能感兴趣的:(二分,线段树)