P8600 [蓝桥杯 2013 省 B] 连号区间数

题目描述

小明这些天一直在思考这样一个奇怪而有趣的问题:

在 ~ 的某个全排列中有多少个连号区间呢?这里所说的连号区间的定义是:

如果区间 里的所有元素(即此排列的第 个到第 个元素)递增排序后能得到一个长度为 的“连续”数列,则称这个区间连号区间。

其中对“连续”数列的定义:

记长度为 的数列 第 个元素为 ,当 时认为该数列为“连续”数列。

当 很小的时候,小明可以很快地算出答案,但是当 变大的时候,问题就不是那么简单了,现在小明需要你的帮助。

输入格式

第一行是一个正整数 , 表示全排列的规模。

第二行是 个不同的数字 , 表示这 个数字的某一全排列。

输出格式

输出一个整数,表示不同连号区间的数目。

输入输出样例 #1

输入 #1

4
3 2 4 1

输出 #1

7

输入输出样例 #2

输入 #2

5
3 4 2 5 1

输出 #2

9

说明/提示

第一个用例中,有 个连号区间分别是:, , , , , , 。

第二个用例中,有 个连号区间分别是:, , , , , , , , 。

原题时限 5 秒, 64M。蓝桥杯 2013 年第四届省赛

解析

本题的题意是:统计有多少个区间 ,计区间最大值为 ,区间最小值为 ,区间长度为 ,满足关系式 。

进行对原关系式的移项,有 。

具体而言,考虑套路地枚举区间右端点 r 进行扫描线,求出以 r 为结尾的符合要求的区间个数,然后再求和。

而为了满足这一要求,我们需要使用单调栈 + 线段树维护。

考虑到 ,可以使用单调栈维护每个后缀的 和 ,再用线段树维护 的最小值以及其出现次数

首先明确,一个区间 是一个连号,当且仅当该区间 。

考虑套路地枚举右端点 r。问题转化为求满足 的区间数量。

对于一个右端点 r,我们可以使用单调栈维护每一个后缀的 ,。

现在问题就是如何快速维护这个数量。注意到 ,因此,用线段树维护区间最值的数量即可。

代码

//
// Created by 23264 on 2026/1/26.
//
#include<bits/stdc++.h>
using namespace std;
 
int N;
vector<int> arr;
int res;
 
// 线段树结构体:维护区间最小值及其出现次数
struct Node
{
    int minn;
    int count;
    int lazy;
}tree[500005];
 
// 向上更新
void pushUp(int rt)
{
    tree[rt].minn = min(tree[rt << 1].minn,tree[rt << 1 | 1].minn);
    tree[rt].count = 0;
    if (tree[rt].minn == tree[rt << 1].minn)
    {
        tree[rt].count += tree[rt << 1].count;
    }
    if (tree[rt].minn == tree[rt << 1 | 1].minn)
    {
        tree[rt].count += tree[rt << 1 | 1].count;
    }
}
 
// 向下下传懒标记
void pushDown(int rt)
{
    if (tree[rt].lazy != 0)
    {
        int v = tree[rt].lazy;
        tree[rt << 1].minn += v;
        tree[rt << 1].lazy += v;
        tree[rt << 1 | 1].minn += v;
        tree[rt << 1 | 1].lazy += v;
        tree[rt].lazy = 0;
    }
}
 
// 初始化线段树:初始时 V[i] = i
void build(int rt,int l,int r)
{
    tree[rt].lazy = 0;
    if (l == r)
    {
        tree[rt].minn = l;
        tree[rt].count = 1;
        return;
    }
    int mid = (l + r) >> 1;
    build(rt << 1,l,mid);
    build(rt << 1 | 1,mid + 1,r);
    pushUp(rt);
}
 
// 区间修改
void update(int rt,int l,int r,int ql,int qr,int val)
{
    if (ql <= l && r <= qr)
    {
        tree[rt].minn += val;
        tree[rt].lazy += val;
        return;
    }
    pushDown(rt);
    int mid = (l + r) >> 1;
    if (ql <= mid)
    {
        update(rt << 1,l,mid,ql,qr,val);
    }
    if (qr > mid)
    {
        update(rt << 1 | 1,mid + 1,r,ql,qr,val);
    }
    pushUp(rt);
}
 
void solve()
{
    // 线段树和单调栈配合
    build(1,0,N - 1);
    stack<int> stkMax; // 维护最大值的单调栈(存下标)
    stack<int> stkMin; // 维护最小值的单调栈(存下标)
 
    for (int j = 0;j < N;j++)
    {
        // 更新最大值对 V_i 的贡献
        while (!stkMax.empty() && arr[stkMax.top()] < arr[j])
        {
            int cur = stkMax.top();
            stkMax.pop();
            int prev = stkMax.empty() ? -1 : stkMax.top();
            // 原来的 max(prev+1...cur) 是 arr[cur],现在变成了 arr[j]
            update(1,0,N - 1,prev + 1,cur,arr[j] - arr[cur]);
        }
        stkMax.push(j);
 
        // 更新最小值对 V_i 的贡献
        while (!stkMin.empty() && arr[stkMin.top()] > arr[j])
        {
            int cur = stkMin.top();
            stkMin.pop();
            int prev = stkMin.empty() ? -1 : stkMin.top();
            // 原来的 min(prev+1...cur) 是 arr[cur],现在变成了 arr[j]
            update(1,0,N - 1,prev + 1,cur,arr[cur] - arr[j]);
        }
        stkMin.push(j);
 
        // 此时根节点的 min_val 记录的是整个区间 [0, j] 中 max - min + i 的最小值
        // 理论最小值就是 j,满足条件即为连号区间
        if (tree[1].minn == j) {
            res += tree[1].count;
        }
    }
}
 
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
 
    if (!(cin >> N))
    {
        return 0;
    }
    arr.resize(N);
    res = 0;
    for (int i = 0;i < N;i++)
    {
        cin >> arr[i];
    }
    solve();
    cout << res << endl;
}
 
// 下面也是一种解法,但是提交会有两个点TLE,需要各种卡常,我就很无奈
// void solve()
// {
//     for (int i = 0;i < N;i++)
//     {
//         int maxx = arr[i];
//         int minn = arr[i];
//         for (int j = i;j < N;j++)
//         {
//             maxx = max(maxx,arr[j]);
//             minn = min(minn,arr[j]);
//             if (maxx - minn == j - i)
//             {
//                 res++;
//             }
//         }
//     }
// }
//
// int main()
// {
//     cin >> N;
//     arr.resize(N);
//     res = 0;
//     for (int i = 0;i < N;i++)
//     {
//         cin >> arr[i];
//     }
//     solve();
//     cout << res;
// }