版权声明:本文为博主原创文章,喜欢就点个赞吧 https://blog.csdn.net/Anxdada/article/details/81742000
传送门
题意: 题意相当于问你改变一个位置之后,从左往右扫描最大值, 这个最大值会改变多少次. 每次改变独立
思路:我们首先要预处理出每一个位置从前往后的答案数, 以及前缀最大值, 还有从后往前的答案数, 前面两个可以边读入边处理, 后面那个需要用到单调队列维护一个最大值来处理, 上次多校出过这样的题. 然后有了这三个东西后, 对于每次的修改, 我们要找的就是修改位置前面是否有比修改后的这个数大的, 然后和自己比较一下, 如果没有则ans += 1 + 前缀处理的答案, 否则就不加1, 然后我们要找的就是比较后的最大值从修改位置往右第一个比它大的位置在哪, 然后ans += 后缀处理的答案即可. 处理判断不存在的情况.
然后这道题最经典的问题就是求某段区间中第一个大于某个值的位置在哪里, (不包含该值的位置), 然后这个有很多方法, 可以用线段树维护每个位置的数字, 然后询问区间最大值, 再树里二分找, 也可以再外面用二分找, 然后还是用rmq或者线段树维护最大值, 都是可以的. 记住这个经典问题.
综合一下复杂度: 外面二分找 + rmq是比较优秀的. 所以就给出这个写法.
AC Code
const int maxn = 1e5+5;
int a[maxn];
int dp1[maxn], dp2[maxn];
void read(int &x){
char ch = getchar();x = 0;
for (; ch < '0' || ch > '9'; ch = getchar());
for (; ch >='0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
}
int dp[maxn][20];
int n, m;
void rmq() {
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i+(1<<(j-1))<=n;i++)
dp[i][j]=max(dp[i][j-1], dp[i+(1<<(j-1))][j-1]);
}
int get_ma(int l, int r) {
int k = 0; if (l > r) return 0;
while((1<<(k+1))<=r-l+1)k++;
return max(dp[l][k] ,dp[r-(1<<k)+1][k]);
}
int Find(int l, int r, int x) {
int ans = -1;
while(l <= r) {
int mid = (l + r) >> 1;
int tmp = get_ma(l, mid);
if (tmp > x) {
ans = mid;
r = mid - 1;
}
else l = mid + 1;
}
return ans;
}
int que[maxn], cnt[maxn];
void solve() {
read(n); read(m);
int mx = 0;
for(int i = 1 ; i <= n ; i ++) {
read(a[i]); dp[i][0] = a[i];
if (a[i] > mx) {
dp1[i] = dp1[i-1] + 1;
mx = a[i];
}
else dp1[i] = dp1[i-1];
cnt[i] = mx;
}
rmq();
int tail = 0, head = 1;
for (int i = n ; i >= 1 ; i --) {
while(head <= tail && que[tail] <= a[i]) --tail;
que[++tail] = a[i];
dp2[i] = (tail - head + 1);
}
// cnt[i] 表示i位置前面最大数是多少, dp1[i]表示前缀的答案, dp2是后缀处理的答案.
while(m--) {
int l, r;
read(l); read(r);
int ans = dp1[l-1]; // 前面是具有可推性质的!!!
if (r > cnt[l-1]) ++ ans;
r = max(r, cnt[l-1]);
int pos = Find(l+1, n, r);
if (pos != -1) ans += dp2[pos];
printf("%d\n", ans);
}
}