hdu 6070 二分答案+线段树

题意: 区间价值为    区间元素种类数  /  区间长度    问最小价值的区间是?


思路:直接求解很困难,考虑二分答案判断,注意这题的关键是将二分答案后的不等式进行变换,如官方题解。二分答案 mid,检验是否存在一个区间满足 size(l,r)/(r−l+1) ≤ mid, 也就是 size(l, r) + mid × l ≤mid × (r + 1)。

之后的问题就很好解决了,枚举右端点,开一颗线段树来维护到当前节点的区间不同数(类似树状数组求区间不同数的想法来加入一个新点,主要是通过一个数组维护上一次出现的位置)+  mid × l ,这样就可以轻松判断是否存在解了。


代码:

#include
using namespace std;
#define MEM(a,b) memset(a,b,sizeof(a))
#define bug puts("bug");
inline int ll(int k) {return 2*k;}
inline int rr(int k) {return 2*k+1;}
inline int mid(int kk1,int kk2) {return (kk1+kk2)>>1;}
const int maxn=3e6+10;
struct pr {
    double sum,lazy;
    int left,right;
}tr[maxn+10];
void pushdown(int k) {
    if(tr[k].lazy>1e-9){
        tr[ll(k)].sum+=tr[k].lazy;tr[rr(k)].sum+=tr[k].lazy;
        tr[ll(k)].lazy+=tr[k].lazy;
        tr[rr(k)].lazy+=tr[k].lazy;
        tr[k].lazy=0;
    }
}
void build(int k,int s,int t,double MI) {
    tr[k].left=s;tr[k].right=t;
    if(s==t) {tr[k].sum=t*MI;return;}
    build(ll(k),s,mid(s,t),MI);
    build(rr(k),mid(s,t)+1,t,MI);
    tr[k].sum=min(tr[ll(k)].sum,tr[rr(k)].sum);
    tr[k].lazy=0;
}
void modify(int k,int s,int t,int x) {
    int l=tr[k].left,r=tr[k].right;
    if(l==s&&r==t) {
        tr[k].lazy+=x,tr[k].sum+=x;
        return ;
    }
    pushdown(k);
    int mi=mid(l,r);
    if(t<=mi) modify(ll(k),s,t,x);
    else if(s>mi) modify(rr(k),s,t,x);
    else modify(ll(k),s,mi,x),modify(rr(k),mi+1,t,x);
    tr[k].sum=min(tr[ll(k)].sum,tr[rr(k)].sum);
}
double query(int k,int s,int t) {
    int l=tr[k].left,r=tr[k].right;
    if(l==s&&r==t) return tr[k].sum;
    pushdown(k);
    int mi=mid(l,r);
    double res=1e20;
    if (t<=mi) res=query(ll(k),s,t);
    else if(s>mi) res=query(rr(k),s,t);
    else res=min(query(ll(k),s,mi),query(rr(k),mi+1,t));
    return res;
}
int a[700000],last[700000],t,n;
int cal(double x){
    build(1,1,n,x);
    MEM(last,0);
    for(int i=1;i<=n;i++){
        modify(1,last[a[i]]+1,i,1);
        last[a[i]]=i;
        if(query(1,1,i)<=x*(i+1)) return 1;
    }
    return 0;
}

int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        double L=0,R=1.0,mi;
        for(int i=0;i<25;i++){
            mi=(L+R)/2;
            if(cal(mi)) R=mi;
            else L=mi;
        }
        printf("%.8f\n",R);
    }
    return 0;
}


你可能感兴趣的:(线段树,三分法/二分法)