题目大意
给你一个数组 a 1 . . . a n a_1...a_n a1...an ,现在有两种操作:
- 用 y y y更新 a x a_x ax
- 询问 l . . . r l...r l...r区间内,有多少非递减子序列
思路
官方题解极其反人类。。。
这是一个经典题的变形,线段树维护区间最大连续和,思路也基本是一样的
用线段树维护每个区间最大递增前缀数量、最大递增后缀数量、以及方案数
然后查询的时候,合并左右子树,这里重载运算符(注释写的很清楚)
代码
// https://codeforces.com/contest/1567/problem/E
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int a[N];
struct Seg_Tree
{
struct node
{
int l, r;
int lmx, rmx;
// ans记录方案数,每次可以合并的时候更新
ll ans;
node operator+(node u) const
{
// 左子树左边界,右子树右边界,左子树最大前缀,右子树最大后缀,两子树方案数和
node res = {l, u.r, lmx, u.rmx, ans + u.ans};
// 中间这段方案数
// 如果左子树的右边界比右子树的左边界小,那么满足递增,可以合并
if (a[r] <= a[u.l])
{
// 左子树最大后缀 * 右子树最大前缀
res.ans += 1ll * rmx * u.lmx;
// 如果左子树整个区间都满足单增
if (lmx == r - l + 1)
// 答案的最长前缀 = 左子树整个区间 + 右子树最大前缀
res.lmx = r - l + 1 + u.lmx;
// 右子树同理
if (u.rmx == u.r - u.l + 1)
res.rmx = u.r - u.l + 1 + rmx;
}
return res;
}
} t[N << 2];
void build(int p, int l, int r)
{
if (l == r)
return t[p] = {l, r, 1, 1, 1}, void();
int mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
t[p] = t[p << 1] + t[p << 1 | 1];
}
void change(int p, int x, int v)
{
if (t[p].l == t[p].r)
return a[x] = v, void();
int mid = (t[p].l + t[p].r) >> 1;
change(p << 1 | (x > mid), x, v);
t[p] = t[p << 1] + t[p << 1 | 1];
}
node ask(int p, int l, int r)
{
if (l <= t[p].l && r >= t[p].r)
return t[p];
int mid = (t[p].l + t[p].r) >> 1;
if (r <= mid)
return ask(p << 1, l, r);
if (l > mid)
return ask(p << 1 | 1, l, r);
// 合并左右答案
return ask(p << 1, l, r) + ask(p << 1 | 1, l, r);
}
} S;
int n, q;
int main()
{
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
S.build(1, 1, n);
while (q--)
{
int t, x, y;
scanf("%d%d%d", &t, &x, &y);
if (t == 1)
S.change(1, x, y);
else
printf("%lld\n", S.ask(1, x, y).ans);
}
return 0;
}