D - Dirt Ratio HDU - 6070詳解 (二分+線段樹區間修改+思維)

這道題目比賽的時候做了半天,結果基本就是掛機的狀態。事後看了題解也是一臉懵逼的。最後看了標程才知道怎麼做的。看懂之後才發現這個這個題目那麼有趣,真是思維太巧妙了(吐槽一下多校賽題解,只有做出了的人才能看懂。(ㄒ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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章