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

问题转化

很容易就能把原问题转化成:

求满足 Max-Min = r-l的区间个数

暴力解法

根据上面得到的性质,我们可以暴力枚举区间,来判断当前区间是否满足性质

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string>
#include <cmath>
#include <string.h>
#define R(x) x = read()
#define For(i, j, n) for (int i = j; i <= n; ++i)
using namespace std;

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
    {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}

const int N = 50005;
typedef long long LL;

int n, a[N];
int Log[N];
int Max[N][17], Min[N][17]; //2^16 = 65536

void init()
{
    memset(Min, 0x3f, sizeof(Min));
    Log[0] = -1;
    for(int i = 1; i <= n; i++)
        Log[i] = Log[i>>1] + 1, Min[i][0] = Max[i][0] = a[i];
    for(int j = 1; j <= 16; j++)
    {   
        for(int i = 1; i + (1 << j - 1) <= n; i++)
            Max[i][j] = max(Max[i][j - 1], Max[i + (1 << j - 1)][j -1]),
            Min[i][j] = min(Min[i][j - 1], Min[i + (1 << j - 1)][j - 1]);
    }
}

int query(int l, int r, int t)
{
    int len = r - l + 1;
    if(t)
        return max(Max[l][Log[len]], Max[r - (1 << Log[len]) + 1][Log[len]]);
    else
        return min(Min[l][Log[len]], Min[r - (1 << Log[len]) + 1][Log[len]]);
}

int main()
{
    R(n);
    For(i, 1, n)
        R(a[i]);
    init();
    LL ans = 0ll;
    for(int len = 1; len <= n; len++)
        for(int i = 1; i + len - 1 <= n; i++)
        {
            int j = i + len - 1;
            ans += (query(i, j, 1) - query(i, j, 0) == len - 1);
        }
    printf("%lld\n", ans);
    return 0;
}

时间复杂度O(n^2),无法在750ms的时间限制下通过本题

分治解

 

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string>
#include <cmath>
#define R(x) x = read()
#define For(i, j, n) for(int i = j ; i <= n ; ++i)
using namespace std;

inline int read()
{
    int x = 0, f = 1; char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
       x = x * 10 + ch - '0';
       ch = getchar();
    }
     return x * f;
}

const int N = 5e4 + 5, M = 1e5 + 5;
typedef long long LL;

int n, a[N];
LL ans;
int cnt[M<<1]; //
int Min[N], Max[N];

void solve(int l, int r) // 当前处理l~r
{
    if(l == r)
    {
        ans++;
        return;
    }
    int mid = (l + r) >> 1;

    // 处理l,r横跨两边的
    Min[mid] = Max[mid] = a[mid];
    // i <= mid, Min,Max表示后缀最值
    for(int i = mid - 1; i >= l; i--)
    {
        Min[i] = min(a[i], Min[i + 1]);
        Max[i] = max(a[i], Max[i + 1]);
    }
    Min[mid + 1] = Max[mid + 1] = a[mid + 1];
    // i > mid, Min,Max表示前缀最值
    for(int i = mid + 2; i <= r; i++)
    {
        Min[i] = min(a[i], Min[i - 1]);
        Max[i] = max(a[i], Max[i - 1]);
    }
    // ----------------最大值在左边
    int Pr1 = mid + 1, Pr2 = mid + 1;
    for(int Pl = mid; Pl >= l; Pl--)
    {
        // 最大值在左边,最小值在右边
        while(Pr1 <= r && Max[Pr1] < Max[Pl]) cnt[Min[Pr1] + Pr1 + M]++, Pr1++; //注意这里可以取等号
        while(Pr2 < Pr1 && Min[Pr2] > Min[Pl]) cnt[Min[Pr2] + Pr2 + M]--, Pr2++;// 这里不能取等号
        // 最后有解的区间是 [Pr2, Pr1 - 1]
        ans += cnt[Max[Pl] + Pl + M];
        // 判断最值均在左边的情况是否成立
        int TmpR = Max[Pl] - Min[Pl] + Pl;
        if(TmpR > mid && TmpR < Pr2) ans++; 
    }
    // ----------------最大值在左边
    for(int i = Pr2; i < Pr1; i++)
        cnt[Min[i] + i + M] = 0; // 清空影响
    // ----------------最大值在右边
    int Pl1 = mid, Pl2 = mid;
    for(int Pr = mid + 1; Pr <= r; Pr++)
    {
        // 最大值在右边,最小值在左边
        while(Pl1 >= l && Max[Pl1] < Max[Pr]) cnt[Min[Pl1] - Pl1 + M]++, Pl1--;
        while(Pl2 > Pl1 && Min[Pl2] > Min[Pr]) cnt[Min[Pl2] - Pl2 + M]--, Pl2--;
        ans += cnt[Max[Pr] - Pr + M];
        // 判断最值均在右边的情况是否成立
        int TmpL = Min[Pr] - Max[Pr] + Pr;
        if(TmpL <= mid && TmpL > Pl2) ans++;
    }
    // ----------------最大值在右边
    for(int i = Pl2; i > Pl1; i--)
        cnt[Min[i] - i + M] = 0; // 清空影响
    solve(l, mid);solve(mid + 1, r);
}

int main()
{
    R(n);
    For(i, 1, n)
        R(a[i]);
    solve(1, n);
    printf("%lld\n", ans);
    return 0;
}

时间复杂度O(nlogn),可以通过

注释已经比较详细,需要额外关注的是:

// 最大值在左边,最小值在右边
        while(Pr1 <= r && Max[Pr1] < Max[Pl]) cnt[Min[Pr1] + Pr1 + M]++, Pr1++; //注意这里可以取等号
        while(Pr2 < Pr1 && Min[Pr2] > Min[Pl]) cnt[Min[Pr2] + Pr2 + M]--, Pr2++;// 这里不能取等号
        // 最后有解的区间是 [Pr2, Pr1 - 1]

从while循环的边界条件中,我们不难看出,最后对应的有解区间是[Pr2,Pr1-1]

1.为什么第一个while是Pr1<=r

因为Max[r]也有可能满足条件,不能落下

2.为什么第二个while不能取等号

因为最后有解的区间是[Pr2,Pr1-1],Pr2<Pr1也就是Pr2<=Pr1-1,就像队列一样,这里的双指针包围区间至少有一个元素时,才能进行“出队”操作,否则,队列本来就已经为空还出队,就会造成指针(下标)越界。

这里有很多相似的变量名,注意不要写错了。

线段树+单调栈

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string>
#include <cmath>
#define R(x) x = read()
#define For(i, j, n) for(int i = j ; i <= n ; ++i)
#define lSon u<<1
#define rSon u<<1|1
using namespace std;

typedef long long LL;

inline int read()
{
    int x = 0, f = 1; char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
       x = x * 10 + ch - '0';
       ch = getchar();
    }
     return x * f;
}

const int N = 50005;

LL ans;
int n, a[N];

struct treeNode{
    int l, r, minVal, cnt, addTag;
    // minVal存储max-min+l
}segTree[N << 2];

void pushUp(int u)
{
    segTree[u].minVal = min(segTree[lSon].minVal, segTree[rSon].minVal);
    int a = segTree[u].minVal==segTree[lSon].minVal?segTree[lSon].cnt:0,
        b = segTree[u].minVal==segTree[rSon].minVal?segTree[rSon].cnt:0;
    segTree[u].cnt = a + b;
}

void build(int u, int l, int r)
{
    segTree[u].addTag = 0;
    segTree[u].l = l;
    segTree[u].r = r;
    if(l == r)
    {
        segTree[u] = {l, r, l, 1, 0};
        return;
    }
    int mid = l + r >> 1;
    build(lSon, l, mid);
    build(rSon, mid + 1, r);
    pushUp(u);
}

void change(int u, int v)
{
    segTree[u].minVal += v;
    segTree[u].addTag += v;
}

void pushDown(int u)
{
    if(segTree[u].addTag == 0)
        return;
    change(lSon, segTree[u].addTag);
    change(rSon, segTree[u].addTag);
    segTree[u].addTag = 0;
}

void updateTree(int u, int l, int r, int val)
{
    if(segTree[u].l >= l && segTree[u].r <= r)
    {
        change(u, val);
        return;
    }
    pushDown(u);
    int mid = segTree[u].l + segTree[u].r >> 1;
    if(l <= mid) updateTree(lSon, l, r, val);
    if(r > mid) updateTree(rSon, l, r, val);
    pushUp(u);
}

int maxStk[N], maxTop, minStk[N], minTop;
//以maxStk为例,maxStk[maxTop]维护的是:[maxStk[maxTop - 1] + 1, maxStk[maxTop]]内的区间max
//单调栈和单调队列一样,存储的都是下标

void solve()
{
    build(1, 1, n);
    for(int i = 1; i <= n; i++)
    {
        //printf("%d\n", i);
        //维护maxStk
        while(maxTop && a[i] > a[maxStk[maxTop]])
        {
            updateTree(1, maxStk[maxTop - 1] + 1, maxStk[maxTop], a[i] - a[maxStk[maxTop]]);
            //如果maxTop=1,栈内只有一个元素,那么maxStk[maxTop - 1] + 1恰好是1,正好就改变了整个区间的max值,这是符合预期的,因此不需要特判
            //printf("case 1:updateTree(1, %d, %d, %d)\n", maxStk[maxTop - 1] + 1, maxStk[maxTop], a[i] - a[maxStk[maxTop]]);
            maxTop--;
        }
        //维护minStk
        while(minTop && a[i] < a[minStk[minTop]])
        {
            updateTree(1, minStk[minTop - 1] + 1, minStk[minTop], a[minStk[minTop]] - a[i]);
            //printf("case 2:updateTree(1, %d, %d, %d)\n", minStk[minTop - 1] + 1, minStk[minTop], a[minStk[minTop]] - a[i]);
            minTop--;
        }
        ans += segTree[1].cnt;
        maxStk[++maxTop] = minStk[++minTop] = i;
    }
}

int main()
{
    R(n);
    For(i, 1, n)
        R(a[i]);
    solve();
    printf("%lld\n", ans);
    return 0;
}

因为以前没有用过单调栈,一直以为它和单调队列的实际含义差不多,导致在理解这个写法的时候卡了很久。

要注意:

单调栈和单调队列一样存储的是下标,所以这里要千万小心,别写错了:

for(int i = 1; i <= n; i++)
    {
        //printf("%d\n", i);
        //维护maxStk
        while(maxTop && a[i] > a[maxStk[maxTop]])
        {
            updateTree(1, maxStk[maxTop - 1] + 1, maxStk[maxTop], a[i] - a[maxStk[maxTop]]);
            //如果maxTop=1,栈内只有一个元素,那么maxStk[maxTop - 1] + 1恰好是1,正好就改变了整个区间的max值,这是符合预期的,因此不需要特判
            //printf("case 1:updateTree(1, %d, %d, %d)\n", maxStk[maxTop - 1] + 1, maxStk[maxTop], a[i] - a[maxStk[maxTop]]);
            maxTop--;
        }
        //维护minStk
        while(minTop && a[i] < a[minStk[minTop]])
        {
            updateTree(1, minStk[minTop - 1] + 1, minStk[minTop], a[minStk[minTop]] - a[i]);
            //printf("case 2:updateTree(1, %d, %d, %d)\n", minStk[minTop - 1] + 1, minStk[minTop], a[minStk[minTop]] - a[i]);
            minTop--;
        }
        ans += segTree[1].cnt;
        maxStk[++maxTop] = minStk[++minTop] = i;
    }

 

posted @ 2024-04-07 17:32  Gold_stein  阅读(8)  评论(0编辑  收藏  举报