zoukankan      html  css  js  c++  java
  • 牛客网多校训练第九场H Cutting Bamboos

    题目链接:https://ac.nowcoder.com/acm/contest/889/H

    题意:给出n颗竹子的高度,q次询问,每次询问给出l,r,x,y,每次选取[l,r]中的竹子,砍y次砍掉所有竹子,每次砍下来的竹子长度和是相同的,问你第x次应该砍在哪个高度上

    解题思路:由于总共砍的次数已经给出,因此我们可以知道砍x次总共砍的量 Total = $sumlimits_{i=l}^{r}$h[i]*x/y,那么问题就变成了一个方程$sumlimits_{i=l}^{r}$max(0,h[i]-ans)=Total,我们需要求的就是ans

    也就是求出区间内大于ans的数减掉ans的和等于Total,利用主席树我们可以求出区间大于一个数的个数num和它们的和sum,接下来我们只需二分ans验证即可。时间复杂度O($nlog^{2}n$),后来我了解到一种更好的做法,

    我们需要取区间大于ans的一段数去计算答案,因此我们递归处理这个区间[L,R],先计算以右区间[M+1,R]的值代入方程,然后以区间中值M(为什么选M,后面会解释)作为假设的ans,这时我们可以得到一个值tmp,令val=Total

    1、如果tmp<Total,说明这个ans选的太大了,我们还需要一个更小的ans,因此递归处理当前区间的[L,M]去找寻更适合的ans,这时递归我们需要让val-=tmp,也就是说,我们只需要传入当前的偏差值进去,在递归到左区间[L,M]时,我们继续选取区间中值ML作为假设的ans,这时我们需要重新计算tmp值,然而当前的Total已经出现了问题(它是根据上一次的假设ans=M计算的),为了修正Total,我们只需在计算tmp时加上前一次减掉的num个M(也就是当前左区间的R,这也是为什么选取M作为假设ans的原因了,我们可以得到上一次的假设ans),然后对当前的假设ans=ML重新计算tmp值,注意,我们要维护一个当前已经选取的竹子数量num,这样我们才可以去计算tmp,计算好tmp值后,继续递归即可

    2、如果tmp>Total,说明这个ans选的太小了,我们需要到右区间[M+1,R]去重新寻找合适的假设ans,因此我们保持参数不变递归右区间即可

    当我们到达递归终点,L==R时,说明,我们如果选择R为假设值仍然存在偏差val(val可能是0,但这没关系),而选择L-1会导致$sumlimits_{i=l}^{r}$max(0,h[i]-ans)>Total,

    因此我们可以根据这个偏差值val去修正假设的ans=L去得到真正的答案,真正的ans=L-val/(以及选取的所有竹子个数+高为L的竹子个数(以L为假设ans时,我们认为高为L的竹子是没有被砍的))时间复杂度O(nlogn)

    AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn = 2e5+5;
    #define eps 1e-8
    struct Node
    {
        int l,r,num;
        ll sum;
    }node[maxn*20];
    int h[maxn],root[maxn],tot;
    ll preSum[maxn];
    void update(int &x,int y,int l,int r,int val)
    {
        x=++tot;
        node[x]=node[y];
        ++node[x].num;
        node[x].sum+=val;
        if(l==r)return;
        int m=(l+r)>>1;
        if(val<=m)update(node[x].l,node[y].l,l,m,val);
        else update(node[x].r,node[y].r,m+1,r,val);
    }
    double query(int x,int y,int L,int R,double val,ll num)
    {
        if(L==R)return L-val/(node[x].num-node[y].num+num);
        int m=(L+R)>>1;
        ll cnt=node[node[x].r].num-node[node[y].r].num;
        ll tmp=node[node[x].r].sum-node[node[y].r].sum-cnt*m+num*(R-m);
        if(tmp+eps<val)return query(node[x].l,node[y].l,L,m,val-tmp,num+cnt);
        return query(node[x].r,node[y].r,m+1,R,val,num);
    }
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(0);
        cout.tie(0);
        int n, q;
        cin >> n >> q;
        for (int i = 1; i <= n; i++) {
            cin >> h[i];
            preSum[i] = preSum[i - 1] + h[i];
            update(root[i], root[i - 1], 1, 1e5, h[i]);
        }
        while (q--) {
            int l, r, x, y;
            cin >> l >> r >> x >> y;
            printf("%.12f
    ", query(root[r], root[l - 1], 1, 1e5, 1.0 * (preSum[r] - preSum[l - 1]) / y * x, 0));
        }
        return 0;
    }
  • 相关阅读:
    JDK的命令详解
    聊天室java socket
    怎么实现利用Java搜索引擎收集网址的程序
    Hibernate实现对多个表进行关联查询
    如何学好J2ME?
    谈谈Java工程师应该具有的知识
    【经营智慧】005.眼光盯着未来
    【成功智慧】002.对任何小事都不要掉以轻心
    【经营智慧】008.要想赚钱,就得打破既有的成见
    【思维智慧】004.砸碎障碍的石头,把它当做钥匙
  • 原文地址:https://www.cnblogs.com/xusirui/p/11361534.html
Copyright © 2011-2022 走看看