2019暑假牛客多校赛第九场H.Cutting Bamboos (主席树+二分)

更多多校信息←请点击这里

H.Cutting Bamboos

传送门

题目描述
There are n bamboos arranged in a line. The i-th bamboo from the left has height h i h_{i} hi.You are given q queries of the type (l, r, x, y). For each query (l, r, x, y) we consider only the l-th to r-th bamboo inclusive. We want to make y horizontal cuts through these bamboos, such that after each cut, the total length of bamboo cut is the same, and after all y cuts there is no bamboo left. For example, if there are 3 bamboos of height 3, 4, 5 respectively and y = 4. The first cut should be at height 3, after which the heights of the bamboos are 3, 3, 3 respectively and the total amount of bamboo cut is 0 + 1 + 2 = 3. Then, the next 3 cuts should be at height 2, 1, 0 respectively. You want to find out what is the height the x-th cut is performed.
Note that after each query, the bamboos are not actually cut, so the heights of the bamboos remain constant after each query.

输入描述:
The first line of input contains two space-separated integers n, q (1 <= n <= 200000, 1 <= q <= 100000).
The next line of input contains n space-separated integers, the i-th of which denotes hi, the height of the i-th bamboo (1 <= hi <= 100000).
The next q lines of input contains 4 space-separated integers each, denoting l, r, x, y (1 <= l <= r <= n, 1 <= x <= y <= 1 0 9 10^9 109).
输出描述:
Output q lines of real numbers, the i-th line contains the answer to the i-th query. Your answer will be accepted if its absolute or relative error is less than 10-6.
输入
5 4
3 5 1 7 4
2 4 3 5
1 4 4 9
1 3 1999 101111
2 2 1 1
输出
2.100000005215406
2.629629638046026
4.822066854685545
0.000000026077032
说明
For the first query, we only consider the bamboos of height 5, 1, 7.
The first cut should be at height 4.7, the total amount of bamboo obtained is 0.3 + 0 + 2.3 = 2.6.
The second cut should be at height 3.4, the total amount of bamboo obtained is 1.3 + 0 + 1.3 = 2.6.
The third cut should be at height 2.1, the total amount of bamboo obtained is 1.3 + 0 + 1.3 = 2.6.
The fourth cut should be at height 13/15, the total amount of bamboo obtained is 37/30 + 2/15 + 37/30 = 2.6.
The fifth cut should be at height 0, the total amount of bamboo obtained is 13/15 + 13/15 + 13/15 = 2.6.
Note that the output values are not exact, but are within the precision requirements.


题目大意:
有n条柱子,高度为 a i a_i ai,我们有q次操作。在l到r的范围内砍y?,将所有的树高都砍为0,但是保证每一?砍出来的长度(砍除树高于该高度的和)都是相同的。问你第x?的时候砍的高度在哪里。有精度误差。每次只对本次操作有影响,操作完后,树回到原来的高度。


题目思路:
想想要解答这道题,
1.就得很快的知道区间中总的树高树高除于y就是每砍一次的后少的长度。 但是由于查询的区间很大,数量也多。我们不可能每次都模拟一遍砍的过程。所以我们就只能直接找到第x次砍下的位置。(那么这里我们就可以用二分来判断位置)。但是具体要怎么判断二分是向上移还是向下移呢。
2.这里我们可以用总树高*x/y得到第x次应砍下的长度和。然后我们就要判断在那个高度时砍下的长度了。我们需要快速的找到范围内有多少棵树大于或等于这个高度的。(很容易联想到主席树,主席树最擅长求区间的有多少个小于k),
3.但是找到这个还不够,我们要算出总的收获,就还得有小于此高度的树的高度和。(主席树维护) 这样子 sum[l~r](区间和)-hi(此高度)*num (大于或等于这个高度的数量) -sum (小于次高度的和)
即:sum[l~r]-hi*num-sum ,就可以判断二分条件了。
在这里插入图片描述
即总和-绿色的数量乘H+黄色的高度。

对于需要区间内在某个范围的信息(如>=k),多用主席树。


ac_code:

//主席树是权值线段树,权值线段树的下标是代表数字的值,那么节点的权值就是代表数字出现的次数。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
int L[maxn<<5],R[maxn<<5];//L每个节点的左子树,R每个节点的右子树,
long long tree[maxn<<5],num[maxn<<5],sum[maxn]; //20到30倍,tree存和,num存数量,sum前缀和
int tot;//结点编号
int n,q;
int f[maxn];
double su,nu;
int build(int l,int r) {
    int root=++tot;
    tree[root]=0;   //空树没有数字存在
    num[root]=0;
    if(l<r){
        int mid=(l+r)>>1;
        L[root]=build(l,mid);
        R[root]=build(mid+1,r);
    }
    return root;
}

int update(int pre,int l,int r,int x) {
    int root=++tot;
    L[root]=L[pre];
    R[root]=R[pre];  //复制线段树
    tree[root]=tree[pre]+x; //先继承前面的线段树,比上一颗树数字个数多x
    num[root]=num[pre]+1; //先继承前面的线段树,比上一颗树数字个数多1
    if(l<r){
        int mid=(l+r)>>1;
        if(x<=mid) L[root]=update(L[pre],l,mid,x); //只有更新左子树,右子树状态和上一颗树一样
        else R[root]=update(R[pre],mid+1,r,x); //只更新右子树,左子树状态和上一颗树一样
    }
    return root;
}

void query(int ql,int qr,int l,int r,int k){ 
    if(l>k) return;  //查询到叶子结点为止
    if(r<=k){   //这个很关键,当这个的时候,得到的就是比k小的树的信息
        su+=tree[qr]-tree[ql]; //比他小的主席树直接求得su
        nu+=num[qr]-num[ql];   //有多少个比他低的数量nu
        return;
    }
    int mid=(l+r)>>1;
    query(L[ql],L[qr],l,mid,k); //找左子树
    query(R[ql],R[qr],mid+1,r,k); //找右子树
}

int main() {
        tot=0;
        memset(sum,0,sizeof(sum));
        scanf("%d%d",&n,&q);
        //这道题不用离散化
        //sort(sum+1,sum+1+n);
        //int d=unique(sum+1,sum+1+n)-sum-1;//去重离散化
        f[0]=build(1,n);//建空树
        for(int i=1;i<=n;i++){
            int hi;scanf("%d",&hi);
            sum[i]=sum[i-1]+hi;  //前缀和高度
            f[i]=update(f[i-1],1,n,hi);  
        }
        while(q--){
            int l,r,x,y;
            scanf("%d %d %d %d",&l,&r,&x,&y);
            double li=0,ri=100000.0,eps=1e-10;
            while(fabs(li-ri)>eps){  //二分高度
                double mid=(li+ri)/2;
                su=0,nu=0;
                query(f[l-1],f[r],1,n,(int)mid);  //比他小的主席树直接求得su,有多少个比他低的数量nu
                nu=(r-l+1)-nu;  
                double s=mid*nu+su; //比他高的数量*mid+比他低的数量
                double step=1.0*(sum[r]-sum[l-1])/y;  //总和/y就是1次砍掉的高度
                if(step*(y-x)<s) ri=mid;
                else li=mid; 
            }
            printf("%.15f\n",li);
        }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值