【BZOJ2957】楼房重建(线段树)

题目

BZOJ2957

分析

题意:给定一个数列 \(a\),动态修改,求前缀最大值的数量。如果 \(i\) 满足 \(\max_{j=1}^{i-1}a_j<a_i\) ,那么 \(a_i\) 是前缀最大值。(真的不是求最长上升子序列的长度!)

考虑用线段树维护,在区间 \([l,r]\) 上维护 \([l,r]\) 的最大值和 子区间 \([l,r]\) 中前缀最大值的数量(注意这里只考虑区间 \([l,r]\) ,不考虑 \([1,l)\) 对它的影响。以下称这个值为「答案」)。合并左右区间时,最大值可以直接合并,而答案的合并比较麻烦:

答案并不是直接把两个子区间的答案相加。左区间的答案可以直接加进当前区间的答案。设左区间的最大值为 \(x\) ,那么右区间所有小于 \(x\) 的前缀最大值都不能算进答案。所以,右区间对当前区间答案的贡献是右区间中「所有大于 \(x\) 的前缀最大值的数量」。

由于前缀最大值一定是单调递增的,所以「大于 \(x\) 的前缀最大值」一定是右区间中前缀最大值的一个后缀,可以通过类似于二分查找的方法解决。具体来说,从当前的右区间往下走,如果左边的最大值大于 \(x\) ,说明左边对 当前正在计算答案 的区间(不是当前走到的区间!)有贡献,则往左边搜,同时加上右边对当前走到的区间的贡献(不是右边的答案!);否则往右边搜。

为了实现这一过程,每个非叶子区间还要记录右区间对该区间的答案的贡献,理论上要按照上面的做法算完记下来,但由于左区间的贡献就是左区间的答案,所以直接用当前区间答案减去左区间答案就是右区间的贡献。时间复杂度 \(O(n\log^2n)\)

代码

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cctype>
using namespace std;
 
namespace zyt
{
    template<typename T>
    inline bool read(T &x)
    {
        char c;
        bool f = false;
        x = 0;
        do
            c = getchar();
        while (c != EOF && c != '-' && !isdigit(c));
        if (c == EOF)
            return false;
        if (c == '-')
            f = true, c = getchar();
        do
            x = x * 10 + c - '0', c = getchar();
        while (isdigit(c));
        if (f)
            x = -x;
        return true;
    }
    template<typename T>
    inline void write(T x)
    {
        static char buf[20];
        char *pos = buf;
        if (x < 0)
            putchar('-'), x = -x;
        do
            *pos++ = x % 10 + '0';
        while (x /= 10);
        while (pos > buf)
            putchar(*--pos);
    }
    const int N = 1e5 + 10;
    namespace Segment_Tree
    {
        struct node
        {
            double mx;
            int ans;
        }tree[N << 2];
        int cal(const int rot, const int lt, const int rt, const double lim)
        {
            if (lt == rt)
                return tree[rot].mx > lim ? 1 : 0;
            int mid = (lt + rt) >> 1;
            if (tree[rot << 1].mx < lim)
                return cal(rot << 1 | 1, mid + 1, rt, lim);
            else
                return tree[rot].ans - tree[rot << 1].ans + cal(rot << 1, lt, mid, lim);
        }
        void update(const int rot, const int lt, const int rt)
        {
            tree[rot].mx = max(tree[rot << 1].mx, tree[rot << 1 | 1].mx);
            int mid = (lt + rt) >> 1;
            tree[rot].ans = tree[rot << 1].ans + cal(rot << 1 | 1, mid + 1, rt, tree[rot << 1].mx);
        }
        void change(const int rot, const int lt, const int rt, const int pos, const double x)
        {
            if (lt == rt)
                return void((tree[rot].mx = x, tree[rot].ans = (x > 0 ? 1 : 0)));
            int mid = (lt + rt) >> 1;
            if (pos <= mid)
                change(rot << 1, lt, mid, pos, x);
            else
                change(rot << 1 | 1, mid + 1, rt, pos, x);
            update(rot, lt, rt);
        }
        int query()
        {
            return tree[1].ans;
        }
    }
    int n, m;
    int work()
    {
        using Segment_Tree::change;
        using Segment_Tree::query;
        read(n), read(m);
        for (int i = 1; i <= n; i++)
            change(1, 1, n, i, 0);
        while (m--)
        {
            int x, y;
            read(x), read(y);
            change(1, 1, n, x, (double)y / x);
            write(query()), putchar('\n');
        }
        return 0;
    }
}
int main()
{
    return zyt::work();
}
posted @ 2020-01-21 17:39  Inspector_Javert  阅读(119)  评论(0编辑  收藏  举报