HDU-6070 Dirt Ratio(二分+线段树+分数规划)

传送门:HDU-6070

上次刚好摔倒在CF Round#427的D题上,这次又来了个类似的题目。。。

题意:要求找一段区间使得区间内不同数的个数/区间长度的比值最小

题解:二分+线段树

设sum为区间内不同数的个数,len为区间长度

我们先二分答案得到k,那么我们需要在序列中找一段区间使得它的sum/len<=k

转换一下得到sum-len*k<=0,这是我们熟悉的分数规划

现在问题就很好解决了,sum可以利用线段树解决:从左往右插入数字,设A[i]上一次出现的位置为pre[i],那么[pre[i]+1,i]这一段权值加1,sum[j]表示的是:区间[j,i]内不同数的个数,这样从左往右插入数字后,所有的区间都被枚举过了,那么还剩下len*k,这个只要每插入一个数A[i],就把[1,i]的权值都减去k即可


#include<bits/stdc++.h>
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define x first
#define y second
#define eps 1e-8
using namespace std;
typedef long long LL;
typedef pair<LL, LL> PII;
const int inf = 0x3f3f3f3f;
const int MX = 6e4 + 5;
const LL mod = 998244353;
int a[MX], n, last[MX], pre[MX];
double sum[MX << 2], add[MX << 2];
void PushDown(int rt) {
    add[rt << 1] += add[rt];
    add[rt << 1 | 1] += add[rt];
    sum[rt << 1] += add[rt];
    sum[rt << 1 | 1] += add[rt];
    add[rt] = 0;
}
void PushUP(int rt) {
    sum[rt] = min(sum[rt << 1], sum[rt << 1 | 1]);
}
void update(int L, int R, double c, int l, int r, int rt) {
    if (L <= l && R >= r) {
        add[rt] += c;
        sum[rt] += c;
        return;
    }
    PushDown(rt);
    int m = (l + r) >> 1;
    if (L <= m) update(L, R, c, lson);
    if (R > m) update(L, R, c, rson);
    PushUP(rt);
}
void build(int l, int r, int rt) {
    sum[rt] = add[rt] = 0;
    if (l == r)return;
    int m = (l + r) >> 1;
    build(lson);
    build(rson);
    PushUP(rt);
}
double query(int L, int R, int l, int r, int rt) {
    if (L <= l && R >= r) return sum[rt];
    PushDown(rt);
    int m = (l + r) >> 1;
    double ret = n;
    if (L <= m) ret = min(ret, query(L, R, lson));
    if (R > m) ret = min(ret, query(L, R, rson));
    PushUP(rt);
    return ret;
}
bool ok(double k) {
    build(1, n, 1);
    for (int i = 1; i <= n; i++) {
        update(pre[i] + 1, i, 1, 1, n, 1);
        update(1, i, -k, 1, n, 1);
        if (query(1, i, 1, n, 1) <= 0) return 1;
    }
    return 0;
}

int main() {
    //freopen("in.txt", "r", stdin);
    int T;
    scanf("%d", &T);
    while (T--) {
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) last[i] = pre[i] = 0;
        for (int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
            pre[i] = last[a[i]];
            last[a[i]] = i;
        }
        double l = 0, r = 1;
        for (int i = 0; i < 20; i++) {
            double m = (l + r) / 2;
            if (ok(m)) r = m;
            else l = m;
        }
        printf("%.5f\n", r);
    }
    return 0;
}


  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值