【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();
}