tyvj4875 排列(单调栈)

Problem

传送门


Solution

自从比赛我打错了这题的60分暴力后,我幼小的内心中留下了一道阴影。
多组数据一定要记得清空全局变量和数组啊啊啊!!!
甚至为了吸取教训,我还准备专门写一篇。。

这题的60分做法就是固定左端点,随着右端点的右移更新最值并贡献答案。考虑100分的做法,当时良久都不知道怎么做,都在想有什么数据结构能大显神通,结果比赛后看了题解,发现做法很玄学。

由于排列是随机的,暴力解法中,固定左端点后,更新最大最小值成功的次数大约是logn级别的(别问我问什么),于是我们只需要预处理出,最大值或最小值变化的那个点,然后中间的那些可以一起算。

我们从右往左左,考虑如何快速求出第一个比i大(小)的j。我们可以利用各种数据结构,线段树平衡树之类的,但是最简洁强大的还是单调栈。我们以求第一个比i大的位置为例,我们维护一个单调下降的的栈(从底到顶),如果在栈顶小于等于当前值就将其弹出,记录栈顶即可。插入则直接插入栈顶。保证栈顶的元素一定是最小的那个。这样假设a[i+1]>a[i],i+1就是答案,否则答案也必然在栈中(只用想想弹出的前提是什么)。另一个栈也差不多。

这样我们在枚举R的时候,同时记录一下[L,R]的最大值和最小值,然后看看哪个先变化就让R跳过去,同时计算答案、更新当前最值就可以了。而这样的时间复杂度是 O(nlogn) 的。


Code

#include <cstdio>
#include <cstring>
#include <iostream>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#define maxn 100010

using namespace std;

typedef long long LL;

int T;
int n, a[maxn];
int st1[maxn], st2[maxn], top1, top2;
int nxtMax[maxn], nxtMin[maxn];
LL sum, ans[maxn];

int main(){

    freopen("sum.in", "r", stdin);
    freopen("sum.out", "w", stdout);

    scanf("%d", &T);

    while(T --){
        scanf("%d", &n);

        top1 = top2 = 0;
        memset(ans, 0, sizeof(ans));

        for(int i = 1; i <= n; i++)  scanf("%d", &a[i]);

        st1[0] = st2[0] = n + 1;
        for(int i = n; i > 0; i--){
            while(top1 && a[st1[top1]] <= a[i])  top1 --;
            while(top2 && a[st2[top2]] >= a[i])  top2 --;
            nxtMax[i] = st1[top1];
            nxtMin[i] = st2[top2];
            st1[++top1] = i;
            st2[++top2] = i;
        }

        for(int L = 1; L <= n; L++){
            for(int R = L, mx = L, mi = L, ne; R <= n; R = ne){
                if(nxtMax[mx] < nxtMin[mi]){
                    ne = nxtMax[mx];
                    ans[a[mx] - a[mi]] += ne - R;
                    mx = ne;
                }
                else{
                    ne = nxtMin[mi];
                    ans[a[mx] - a[mi]] += ne - R;
                    mi = ne;
                }
            }
        }
        sum = 0LL;
        for(int i = 0; i < n; i++){
            sum += ans[i];
            printf("%lld\n", sum);
        }
    }

    return 0;
}

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值