[BZOJ 4293] [PA 2015] Siano

BZOJ链接

题目描述

农夫Byteasar买了一片n亩的土地,他要在这上面种草。
他在每一亩土地上都种植了一种独一无二的草,其中,第i亩土地的草每天会长高a[i]厘米。
Byteasar一共会进行m次收割,其中第i次收割在第d[i]天,并把所有高度大于等于b[i]的部分全部割去。Byteasar想知道,每次收割得到的草的高度总和是多少,你能帮帮他吗?

输入格式

第一行包含两个正整数n,m(1<=n,m<=500000),分别表示亩数和收割次数。
第二行包含n个正整数,其中第i个数为ai,依次表示每亩种植的草的生长能力。
接下来m行,每行包含两个正整数d[i],bi,依次描述每次收割。
数据保证 d[1]<d[2]<...<d[m] d [ 1 ] < d [ 2 ] < . . . < d [ m ] ,并且任何时刻没有任何一亩草的高度超过10^12。

输出格式

输出m行,每行一个整数,依次回答每次收割能得到的草的高度总和。

输入样例

4 4
1 2 4 3
1 1
2 2
3 0
4 4

输出样例

6
6
18
0

提示

第1天,草的高度分别为1,2,4,3,收割后变为1,1,1,1。

第2天,草的高度分别为2,3,5,4,收割后变为2,2,2,2。

第3天,草的高度分别为3,4,6,5,收割后变为0,0,0,0。

第4天,草的高度分别为1,2,4,3,收割后变为1,2,4,3。

解题分析

隔壁ShadyPi dalao的非时间戳版本线段树sto 传送门 orz, 蒟蒻并不会不打时间戳的办法…

当时在UESTC并没有打出这道题, 以为是树套树, 因为似乎需要维护很多东西, 于是打了30分的 O(nm) O ( n m ) 暴力(然而最后居然爆0了QAQ)

然而其实并不用树套树, 因为这里有一个隐藏的优秀的性质:生长速度快的草永远不会比生长速度慢的草矮。所以我们先将草按速度排序, 再将所有的草在一棵线段树建点, 那么可以确定的是每次割草的时候割下的都是一个连续区间的草。

那么如何知道哪一段区间需要被割下呢?暴力pushdown显然不现实。

我们可以维护每个区间的最大值和最小值及高度总和和速度总和, 设需割下的草的高度为height,若 min(valseg)>height m i n ( v a l s e g ) > h e i g h t , 则说明当前区间所有的草都需要被割下, 直接打上lazy标记, 更新区间的高度总和。若 max(valseg)<height m a x ( v a l s e g ) < h e i g h t ,则说明当前区间所有的草都不需要被割, 直接返回0。其他情况时我们pushdown一层递归处理。

值得注意的是, 因为可能有一段区间经过几段割草操作而没有被割, 而每次的总高度、增量需要我们维护上一次割草操作的时间, 但我们pushdown一层的时候需要最近一次成功被割后的时间(因为在此期间其实下一层的草全部都在生长), 所以我们还需要维护一个值las。

代码如下:

#include 
#include 
#include 
#include 
#include 
#define R register
#define gc getchar()
#define W while
#define IN inline
#define MX 1000005
#define ll long long
template <typename T>
IN void in (T &x)
{
    x = 0; R char c = gc;
    W (!isdigit(c)) c = gc;
    W (isdigit(c))
    {x = (x << 1) + (x << 3), x += c - 48, c = gc;}
}
namespace SGT
{
    #define ls now << 1
    #define rs now << 1 | 1
    int sped[MX];
    int dot, q;
    struct Node
    {
        ll las ,tim, cut, mx, mn, sum, spd;
        //cut 记录 最近一次成功被割的高度, 
        //tim记录最近一次被割(不一定成功)的时间,
        //las记录最近一次成功被割的时间,
        //mx,mn记录区间最高草的高度与最矮草的高度
        //sum记录区间草的高度的总和
        //spd记录区间草生长速度的总和
    }tree[MX << 1];
    IN void pushup(const int &now) 
    {tree[now].spd = tree[ls].spd + tree[rs].spd;}
    //
    void build(const int &now, const int &lef, const int &rig)
    {
        tree[now].cut = -1;
        if(lef == rig)
        {
            tree[now].spd = sped[lef];
            return;
        }
        int mid = (lef + rig) >> 1;
        build(ls, lef, mid);
        build(rs, mid + 1, rig);
        pushup(now);
    }
    IN void pushdown(const int &now, const int &lef, const int &rig)
    {
        int mid = (lef + rig) >> 1;
        if(~tree[now].cut)
        {//没有标记时记为-1, 补码为1111 1111, 取反后为0000 0000
            tree[ls].cut = tree[rs].cut = tree[now].cut;
            tree[ls].las = tree[rs].las = tree[ls].tim = tree[rs].tim = tree[now].las;
            tree[ls].sum = tree[ls].cut * (mid - lef + 1);
            tree[rs].sum = tree[rs].cut * (rig - mid);
            tree[ls].mn = tree[rs].mn = tree[ls].mx = tree[rs].mx = tree[now].cut;
            tree[now].cut = -1;
        }
    }
    ll query (const int &now, const int &lef, const int &rig, const ll &ct, const ll &tm)
    {
        ll ret;
        tree[now].sum += (tm - tree[now].tim) * tree[now].spd;
        tree[now].mn += (tm - tree[now].tim) * sped[lef];
        tree[now].mx += (tm - tree[now].tim) * sped[rig];
        tree[now].tim = tm;
        if(tree[now].mx <= ct) return 0;//情况一
        else if(tree[now].mn > ct)
        {//情况二
            ret = tree[now].sum - ct * (rig - lef + 1);
            tree[now].sum = ct * (rig - lef + 1);
            tree[now].mx = tree[now].mn = ct;
            tree[now].cut = ct;
            tree[now].tim = tree[now].las = tm;
            return ret;
        }
        pushdown(now, lef, rig);
        int mid = (lef + rig) >> 1;
        ret = query(ls, lef, mid, ct, tm);
        ret += query(rs, mid + 1, rig, ct, tm);
        tree[now].sum = tree[ls].sum + tree[rs].sum;
        tree[now].mn = tree[ls].mn;//显然左儿子的最小值就是区间最小值
        tree[now].mx = tree[rs].mx;//右儿子的最大值就是区间最大值
        return ret;
    }
}
using namespace SGT;
using std::sort;
int main(void)
{ 
    ll day, bound;
    in(dot), in(q);
    for (R int i = 1; i <= dot; ++i) in(sped[i]);
    sort(sped + 1, sped + 1 + dot);
    build(1, 1, dot);
    W (q--)
    {
        in(day), in(bound);
        printf("%lld\n", query(1, 1, dot, bound, day));
    }
}

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