hdu6070 Dirt Ratio (线段树:二分+多次建树+构造难想)

题目:

给一个 n 个数的序列,选出一段区间,使得这段区间 的值最小,输出这个最小值。误差不要超过1e-4,1 <= n <= 60000,1 <= ai <= n

分析:

这道题看到之后应该能想出来是二分,这种题通常都需要二分。但是没有想出二分完怎么判断是否可行。

判断也就是判断是否有一个区间满足 size(l,r)rl+1mid 。看见这个式子感觉还是没法很快判断是否有这个区间,因为 l 和 r 都不固定。答案很巧妙,将这个式子整理一下,变成 size(l,r)+mid×lmid×(r+1) ,在这个式子中,将 mid 看作是个定值是关键。现在我们枚举区间两个点肯定不允许,所以就想只枚举一个。枚举右端点,那么右端点 r 也是个定值。要想让不等式成立,我们只需让式子左边的最小值小于等于右边就可以了。

所以问题就变成了右端点 r 固定的情况下,如何求左边最小值。想到用线段树维护 size(l,r)+mid×l 。查询的时候查询 [1,r] 这一截的最小值即可,每次查询左端点不变。更新的话,区间右端点是当前这个数字,左端点是当前这个数字上次出现的位置的后一个。将这一截打上标记即可。

反思。完全没想到可以多次建树,思维还是很固定。而且那个不等式想不到去整理转化。因为是不等式,并非等式,所以确定一个区间不一定要两个端点,固定一个找最值也可以。

代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
#define ms(a,b) memset(a,b,sizeof(a))
#define lson rt*2,l,(l+r)/2
#define rson rt*2+1,(l+r)/2+1,r
typedef long long ll;
const int MAXN = 6e4+5;
const double EPS = 1e-5;
const int INF = 0x3f3f3f3f;
int pre[MAXN],a[MAXN],tag[MAXN << 2],n;
double v[MAXN << 2];
void pushup(int rt){
    v[rt] = min(v[rt<<1],v[rt<<1 | 1]);
}
void putTag(int rt,int x){
    tag[rt] += x;
    v[rt] += x;
}
void pushdown(int rt){
    if(tag[rt]){
        putTag(rt << 1,tag[rt]);
        putTag(rt << 1 | 1,tag[rt]);
        tag[rt] = 0;
    }
}
void build(int rt,int l ,int r,double mid){
    v[rt] = mid * l;
    tag[rt] = 0;
    if(l==r)    return;
    build(lson,mid);
    build(rson,mid);
    pushup(rt);
}
void update(int L,int R,int rt,int l,int r){
    if(L <= l && R >= r){
        putTag(rt,1);
        return;
    }
    pushdown(rt);
    if(L <= (l+r)/2)    update(L,R,lson);
    if(R > (l+r)/2) update(L,R,rson);
    pushup(rt);
    return;
}
double query(int R,int rt,int l,int r){
    if(R >= r){
        return v[rt];
    }
    pushdown(rt);
    if(R <= (l+r)/2)    return query(R,lson);
    return min(query(R,lson),query(R,rson));
}
int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
        }
        double l = 0.0,r = 1.0,ans;
        while(abs(l-r) >= EPS){
            double mid = (l+r)/2;
            build(1,1,n,mid);
            ms(pre,0);
            int i;
            for(i=1;i<=n;i++){ 
                update(pre[a[i]]+1,i,1,1,n);
                if(query(i,1,1,n) <= mid*(i+1)) break;
                pre[a[i]] = i;
            }
            if(i<=n){
                r = mid - 0.00001;
                ans = mid;
            }else{
                l = mid + 0.00001;
            }
        }
        printf("%f\n", ans);
    }
    return 0;
}

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