题意:Taotao有N个苹果排成一条直线,他从第一个开始取苹果,每个苹果都有一个值Hi,除了第一个苹果外,他每次只会取比前一次Hi值高的苹果。有M次询问(p,q),如果将第p个苹果的值改成q,求他最多能取多少个苹果。
思路:因为序列是严格上升的,所以初始的时候取哪些苹果是一定的,我们可以预先处理出来。对于每次询问,可知p位置前面选取的苹果是一定的,用lcs[i]记录初始时到i点取到的苹果数。
下面就开始讨论,通过RMQ查询到p位置之前取到的最大苹果res,和q比较,如果q更大,那么新的苹果一定能被取到,++ans,当前取到的最大苹果更新为q;否则新的苹果不会被取到,当前取到的最大苹果还是res。
之后是p位置之后的部分,显然,后面的苹果要取的苹果一定要比前面最大的苹果还要大,利用RMQ查询[p + 1, n]区间内第一个比res或q大的值即可。
我们事先做一个预处理,cnt[i]表示从第i个苹果开始取,最多能取到的苹果数,用单调栈即可维护出来。
有两次用到RMQ,对于第二个地方,我们只需要在RMQ原的基础上做改动即可,如果左部分的最大值大于q,就递归查询左部分;如果右部分的最大值大于q,就递归查询右部分,否则返回-1.
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <stack>
#include <cmath>
#include <list>
#include <cstdlib>
#include <set>
#include <string>
#include <time.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = 100005;
int n, m;
int st[maxn][21], h[maxn], Log[maxn], pos[maxn][21];
int nxt[maxn], sta[maxn], cnt[maxn], lcs[maxn];
void ST() {
for (int i = 0; i < n; ++i) {
st[i][0] = h[i];
pos[i][0] = i;
Log[i + 1] = log(i + 0.5) / log(2.0);
}
Log[1] = 0;
for (int j = 1; j <= Log[n]; ++j) {
for (int i = 0; i <= n - (1 << j); ++i) {
if (st[i][j - 1] >= st[i + (1 << ( j - 1))][j - 1]) {
st[i][j] = st[i][j - 1];
pos[i][j] = pos[i][j - 1];
} else {
st[i][j] = st[i + (1 << ( j - 1))][j - 1];
pos[i][j] = pos[i + (1 << ( j - 1))][j - 1];
}
}
}
}
int query(int l, int r) {
int k = Log[r - l + 1];
return st[l][k] >= st[r - (1 << k) + 1][k] ? pos[l][k] : pos[r - (1 << k) + 1][k];
}
int qry(int l, int r, int q) {
if (l > r) {
return -1;
}
if (l == r) {
return st[l][0] > q ? pos[l][0] : -1;
}
int k = Log[r - l + 1];
if (st[l][k] > q) {
return qry(l, l + (1 << k) - 1, q);
} else if (st[r - (1 << k) + 1][k] > q) {
return qry(r - (1 << k) + 1, r, q);
} else {
return -1;
}
}
int main(){
int t, q, l, r, p;
scanf("%d", &t);
while (t--) {
scanf("%d%d", &n, &m);
for (int i = 0; i < n; ++i) {
scanf("%d", &h[i]);
}
ST();
int len = 0, cur = 0;
memset(cnt, 0, sizeof(cnt)); // 此点以后的苹果数
memset(nxt, 0, sizeof(nxt)); // i以后第一个大于当前点的位置
memset(lcs, 0, sizeof(lcs)); // 到当前点的苹果数
//预处理
for (int i = 0; i < n; ++i) {
// 单调栈求出nxt
while (len > 0 && h[i] > h[sta[len - 1]]) {
--len;
nxt[sta[len]] = i;
}
sta[len++] = i;
// 处理出lcs
if (i == 0) {
lcs[i] = 1;
cur = h[i];
} else if (h[i] > cur) {
cur = h[i];
lcs[i] = lcs[i - 1] + 1;
} else {
lcs[i] = lcs[i - 1];
}
}
for (int i = n - 1; i >= 0; --i) {
// 利用nxt求出cnt
if (nxt[i] == 0) {
cnt[i] = 1;
} else {
cnt[i] = cnt[nxt[i]] + 1;
}
}
while (m--) {
scanf("%d%d", &p, &q);
int ans = 0, pd;
if (p == 1) {
pd = qry(1, n - 1, q);
ans += 1;
} else {
int res = query(0, p - 2);// p之前取到的最大苹果的下标
ans += lcs[res];
if (q > h[res]) {
ans += 1;
pd = qry(p, n - 1, q); // p之后第一个大于前面所有苹果的下标
} else {
pd = qry(p, n - 1, h[res]);
}
}
if (pd < n && pd >= 0) {
ans += cnt[pd];
}
printf("%d\n", ans);
}
}
return 0;
}
附一个ST表的板子:
const int maxn = 100005;
int n, m;
int st[maxn][21], Log[maxn], pos[maxn][21];
/*
st[i][j]:从i开始长度为2^j区间内的最大值
即 st[i][j] = max(h[i], h[i + 1],...,h[i + (1 << j) - 1])
pos[i][j]:记录最大值的下标
*/
void ST() {
for (int i = 0; i < n; ++i) {
st[i][0] = h[i];
pos[i][0] = i;
Log[i + 1] = log(i + 0.5) / log(2.0);
}
Log[1] = 0;
for (int j = 1; j <= Log[n]; ++j) {
for (int i = 0; i <= n - (1 << j); ++i) {
st[i][j] = max(st[i][j - 1], st[i + (1 << ( j - 1))][j - 1]);
/*
// 同时维护下标
if (st[i][j - 1] >= st[i + (1 << ( j - 1))][j - 1]) {
st[i][j] = st[i][j - 1];
pos[i][j] = pos[i][j - 1];
} else {
st[i][j] = st[i + (1 << ( j - 1))][j - 1];
pos[i][j] = pos[i + (1 << ( j - 1))][j - 1];
}
*/
}
}
}
int query(int l, int r) {
int k = Log[r - l + 1];
return max(st[l][k], st[r - (1 << k) + 1][k]);
// 返回下标
//return st[l][k] >= st[r - (1 << k) + 1][k] ? pos[l][k] : pos[r - (1 << k) + 1][k];
}