D - Dirt Ratio HDU - 6070详解 (二分+线段树区间修改+思维)

本文介绍了一种结合二分搜索与线段树数据结构解决特定问题的方法。通过枚举答案并利用线段树进行区间更新与查询,实现了高效求解。详细解析了算法思路,并附带完整代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这道题目比赛的时候做了半天,结果基本就是挂机的状态。事后看了题解也是一脸懵逼的。最后看了标程才知道怎么做的。看懂之后才发现这个这个题目那么有趣,真是思维太巧妙了(吐槽一下多校赛题解,只有做出了的人才能看懂。(ㄒoㄒ))。
他是这样解决的二分枚举答案,对于答案如果成立那么一定存在size(l, r)/(r-l+1) < = mid.所以化简得到size(l, r) + l * mid <= (r + 1) * mid.
  如何在快速的解决这个呢?就是要用到线段树,首先初始化整个树的时候每个节点保存的是mid * l的值,然后取枚举右边界r。对于每个枚举的r的这种状态下,线段树(L, R)中存放的是,左区间在(L, R) 中,右区间为r的元素种类+l*mid的最小值。当r增加到r+1的时候(在处理的时候会有一个app数组记录当前元素出现的上一个位置),对于整个线段树的影响就是当前位置与当前元素出现的上一位置,这个区间加1。
  对于二分的时间复杂度为logn,枚举右端点为n,对于每次枚举都要一次区间修改操作是logn,一次查询操作 logn.所以一共为o(n * logn * logn).
代码如下:


#include <iostream>
#include <stdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
#define Max_N (6*10000+100)
double v[4*Max_N];
int addv[4*Max_N];
int n;
int a[Max_N];
double min1 ;
void build(int node, int l, int r, double m) {
    v[node] = m * l; 
    addv[node] = 0;
    if (l == r) return;
    int m1 = l + (r - l) / 2;
    build(node*2, l, m1, m);
    build(node * 2 + 1, m1 + 1, r, m);

}

int add1(int node, int x)
{
    v[node] += x;
    addv[node] += x;
}

void pushdown(int node)
{
    if (addv[node]) {
        add1(node * 2, addv[node]);
        add1(node * 2 + 1, addv[node]);
    }
    addv[node] = 0;
}

void change(int node, int l, int r, int y1, int y2)
{
    if (y1 <= l && r <= y2) {
        add1(node, 1);
        return;
    }
    pushdown(node);
    int m = l + (r - l) / 2;
    if (m >= y1) change(node * 2, l, m, y1, y2);
    if (m+1 <= y2) change(node * 2 + 1, m + 1, r, y1, y2);
    v[node] = min(v[node*2], v[node*2+1]);
}

double query(int node, int l, int r, int y1, int y2)
{
    if (y1 > r || y2 < l) return 100000000;
    if (r <= y2 && l >= y1) {
        return v[node];
    }
    pushdown(node);
    int m = l + (r - l) / 2;
    return min(query(node * 2, l, m, y1, y2), query(node * 2 + 1, m + 1, r, y1, y2));

}

int app[Max_N];
int main()
{ 
    int T;
    cin >> T;
    while (T--) {
        scanf("%d", &n);
        for (int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        double l = 0; double r = 1;
        int flag = 0;
        while (true) {
            double mid = l + (r - l) / 2;
            build(1, 1, n, mid);
            flag = 0;
            memset(app, 0, sizeof(app));
            //cout << mid << endl;
            for (int i = 1; i <= n; i++) {
                change(1, 1, n, app[a[i]] + 1, i);

                min1 = query(1, 1, n, 1, i);
                if (min1 <= mid * (i*1.0 + 1)) {
                    flag = 1;
                    break;
                }
                app[a[i]] = i;
            }
            if (flag) r = mid;
            else l = mid;
            if (r - l <= 0.000001) break;
        }

        printf("%.10lf\n", (l + r) / 2);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值