hdu6070 線段樹+二分

題意給你一個區間,讓你找,區間種類數/區間長度最小是多少。
思路先公式化簡 size/(R - L + 1) <= mid –> size + (L - 1)mid <= R * mid;(mid是二分的值)
對於每一個元素a[i],他能影響的區間是,last[a[i]] + 1 , 到i(last表示這個這個元素上一次出現的位置),他能讓這個區間的種類數+1,由於我們需要快速的找到上面的最小的一坨(size + (L - 1)*mid),所以應該用線段樹來維護這一坨。然後就是線段樹插的問題。(這個問題我想了好久好久,然後這麼回事,每一次,你就想他的L,R都是他的座標,那麼這個L就是當前的i)然後在每個i位置,把上面的一坨插進去。插進去之後,發現他能影響前面的一段區間,就把那個區間整體的值+1(不能把所有的插完再詢問區間,因爲你全部插完之後,可能後面的元素不選,但是他還是影響了前面的元素,所以不能選)。
插完之後,找到最小值,比較一下就可以了(和之前二分的答案比)。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <cmath>
#include <cstdlib>
#include <ctime>
using namespace std;
typedef long long LL;

const int N = 1e5 + 10;

double setv[N * 4] , minv[N * 4] , v , _min;
int pl , pr , n;
int last[N] , now[N] , A[N];

void pushup(int o , int L , int R)
{
    int lc = o * 2 , lr = o * 2 + 1 , M = (L + R) / 2;
    minv[o] = 0;
    if(L != R){
        minv[o] = min(minv[lc] , minv[lr]);
    }
    minv[o] += setv[o];
}

void update(int o , int L , int R)
{
    int lc = o * 2 , lr = o * 2 + 1 , M = (L + R) / 2;
    if(pl <= L && pr >= R) setv[o] += v;
    else{
        if(pl <= M) update(lc , L , M);
        if(pr > M) update(lr , M + 1 , R);
    }
    pushup(o , L , R);
}

void query(int o , int L , int R , double add)
{
    int lc = o * 2 , lr = o * 2 + 1 , M = (L + R) / 2;
    if(pl <= L && pr >= R) _min = min(_min , minv[o] + add);
    else{
        if(pl <= M) query(lc , L , M , setv[o]);
        if(pr > M) query(lr , M + 1 , R , setv[o]);
    }
}

bool check(double ratio)
{
    memset(setv , 0 , sizeof(setv));
    for(int i = 1 ; i <= n ; i++){
        pl = i , pr = i , v = 1 + (i - 1) * ratio;
        update(1 , 1 , n);
        pl = last[i] + 1;
        v = 1;
        pr = i - 1;
        if(pl <= pr) update(1 , 1 , n);
        pl = 1 , pr = i , _min = N;
        query(1 , 1 , n , 0);
        if(_min <= i * ratio) return 1;
    }
    return 0;
}

int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        memset(last , 0 , sizeof(last));
        memset(now , 0 , sizeof(now));
        for(int i = 1 ; i <= n ; i++) scanf("%d",&A[i]);
        for(int i = 1 ; i <= n ; i++){
            last[i] = now[A[i]] , now[A[i]] = i;
        }
        double l = 0 , r = 1 , m;
        for(int i = 1 ; i <= 30 ; i++){
            m = (l + r) / 2;
            if(check(m)) r = m;
            else l = m;
        }
        printf("%lf\n",r);
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章