2019牛客暑期多校训练营(第九场)Cutting Bamboos (主席树+二分)

题意:

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

给你 n 棵树每一棵树的高度,每一棵树从左到右编号为 1, 2, ... n 。Q 次询问,每次询问(l, r, x, y)代表在编号为 [l, r] 这个闭区间内的树,我需要砍 y 次把这些树砍成高度为 0 ,但是每次砍只能规定一个高度,这个高度以上的需要全部砍去,下面的不动,且每次砍的高度之和需要相同, 比如 高度为 [6,2,1]的三棵树 我规定 y = 3,那么第一次规定砍的高度为3,之后砍成 [3,2,1] ,第二次规定砍的高度为1,之后砍成[1,1,1],第三次规定砍的高度为0,之后砍成 [0, 0, 0] 。那么x就是询问砍第x次时候需要规定的高度。每次询问都是独立的,树木还是最初没有被砍过的树木。

解题思路:

对于区间[l, r]的树木,首先我可以使用前缀和求出[l, r] 所有树木的高度之和 sum,然后除以需要砍的次数 y。就可以求出来每次需要砍掉的高度,用每次需要砍掉的高度乘以 x ,就是总的需要砍掉的树木的高度和。那么现在问题就转化为:求 ans 使得 ans 以上的树木的高度和为 sum / y * x 。那么我们只需要二分这个ans,每次求一个被砍去的树的高度和与 sum / y *  x 比较就可以。现在问题是给你一个高度ans怎么求出在[l, r]之间树木在ans以上的高度的和。就可以使用主席树来维护每种树的个数。到时候只需要在区间 [l, r] 之间查找比 ans 高的树木有多少颗 (假设为 cut_num 棵),还有比ans高的树木的总高度(假设为 cut_sum ),然后用      cut_sum - cut_num * ans 这个就是在[l, r]之间树木在ans以上的高度的和。具体细节可以参考AC代码。

AC代码:

#include
#define up(i, x, y) for(ll i = x; i <= y; i++)
#define down(i, x, y) for(ll i = x; i >= y; i--)
#define bug prllf("*********\n")
#define debug(x) cout<<#x"=["<> 1;
    if(pos <= mid) update(l, mid, T[x].l, T[y].l, pos);
    else update(mid + 1, r, T[x].r, T[y].r, pos);
}

void query(ll l, ll r, ll x, ll y, ll pos)
{
    if(r < pos) return ;
    if(pos <= l) // 找大于等于 mid 的
    {
        cut_num += T[y].num - T[x].num;
        cut_sum += T[y].sum - T[x].sum;
        return ;
    }
    ll mid = (l + r) >> 1;
    query(l, mid, T[x].l, T[y].l, pos);
    query(mid + 1, r, T[x].r, T[y].r, pos);
}

int main()
{
    scanf("%lld %lld", &n, &q);
    len = 100000;
    for(ll i = 1; i <= n; i++)
    {
        scanf("%lld",&h[i]);
        sum[i] = sum[i - 1] + h[i]; // 求前缀和,为后面计算总的高度和
    }
    for(ll i = 1; i <= n; i++) update(1, len, root[i], root[i - 1], h[i]); // 建树
    while(q--)
    {

        scanf("%lld %lld %lld %lld", &l, &r, &x, &y);
        double S_cut = (sum[r] - sum[l - 1]) * 1.0 * x / y; 
        // 需要砍掉的面积,先乘后除,减小误差。
        ll L = 1, R = 100000;
        while(L <= R)
        {
            ll mid = (L + R) / 2.0;
            cut_num = 0; // 大于等于mid的树木的个数
            cut_sum = 0; // 大于等于mid的树木的高度和
            query(1, len, root[l - 1], root[r], mid);
            if( S_cut > 1.0 * (1.0 * cut_sum - 1.0 * (mid - 1) * cut_num) ) 
// 因为我查找的是大于等于 mid 的树木高度的和,
// 所以我底下没有砍去的部分应该为 (mid - 1) * cut_num。
            {
                ans = mid; 
// 这样二分出来是一个正数,再此高度以上的和其实是小于 S_cut 的,
// 所以答案是在 [ans - 1, ans] 这个范围,具体的范围在后面精确求解。
                R = mid - 1; // 砍的不够多,水平降低,多砍一些
            }
            else
            {
                L = mid + 1;
            }
        }
// 开始精确求解,可以画一个图就知道这些公式了,很简单,就是知道面积,
// 知道底,求一个高 。 答案是在 [ans - 1, ans] 这个范围,上面也提到了
        cut_num = 0; // 相当于底
        cut_sum = 0; 
        query(1, len, root[l - 1], root[r], ans - 1); 
        double del = S_cut - 1.0 * (1.0 * cut_sum - 1.0 * ans * cut_num);
        // 相当于还需要砍去的树的高度和,相当于面积
        double hh = del / cut_num; // 还需要砍去的高
        hh = 1.0 - hh;             // 剩下的高
        printf("%.15f\n", ans * 1.0 - 1.0 + hh);
        // ans往下一个高度,然后加上剩下的高就是精确答案了。
    }
}

 

 

 

 

你可能感兴趣的:(ACM,数据结构,2019牛客暑期多校训练营)