ST表一些练习题答案

        ST表最好不要只去背模板,搞明白这个算法的原理,才能在做题时用到就能想到,灵活运用

        做题时一定注意是否需要开 long long

P3865 【模板】ST 表

板子题。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int T, n, m;
int f[N][30];
int a[N];

signed main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for(int i = 1; i <= n; i ++)
    {
        cin >> a[i];
        f[i][0] = a[i];
    }
    for(int j = 1; (1 << j) <= n; j ++)
        for(int i = 1; i+(1 << j)-1 <= n; i ++)
            f[i][j] = max(f[i][j-1], f[i+(1<<(j-1))][j-1]);
    while(m --)
    {
        int l, r;
        cin >> l >> r;
        int s = log2(r-l+1);
        int ans = max(f[l][s], f[r-(1<<s)+1][s]);
        cout << ans << '\n';
    }
    return 0;
}

P2251 质量检测

板子题。 

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
int T, n, m;
int a[N], f[N][30];

signed main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for(int i = 1; i <= n; i ++)
    {
        cin >> a[i];
        f[i][0] = a[i];
    }

    for(int j = 1; (1 << j) <= n; j ++)
        for(int i = 1; i + (1 << j) - 1 <= n; i ++)
            f[i][j] = min(f[i][j-1], f[i+(1<<(j-1))][j-1]);
            
    int s = log2(m);
    for(int i = 1; i <= n-m+1; i ++)
    {
        int l = i, r = i + m - 1;
        cout << min(f[l][s], f[r - (1 << s) + 1][s]) << '\n';
    }
    return 0;
}

 P2880 [USACO07JAN] Balanced Lineup G

分别对最大最小值 做个ST表,求个差值即可

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5+5;
typedef pair<int, int>PII;
const int Max = 0x3f3f3f3f3f3f3f3f;
const int Min = -0x3f3f3f3f3f3f3f3f;
int T, n, m;
int mi[N][30], mx[N][30];
int lg[N];
int rmq(int l, int r, int x)
{
    int s = lg[r-l+1];
    if(x == 0) return min(mi[l][s], mi[r-(1<<s)+1][s]);
    return max(mx[l][s], mx[r-(1<<s)+1][s]); 
}
signed main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for(int i = 1; i <= n; i ++)
    {
        int x;
        cin >> x;
        mi[i][0] = mx[i][0] = x;
    }

    for(int i = 2; i <= n; i ++) lg[i] = lg[i >> 1] + 1;
    int t = lg[n];

    for(int j = 1; j <= t; j ++)
        for(int i = 1; i + (1 << j) - 1 <= n; i ++)
        {
            mi[i][j] = min(mi[i][j-1], mi[i+(1<<j-1)][j-1]);
            mx[i][j] = max(mx[i][j-1], mx[i+(1<<j-1)][j-1]);
        }

    while(m --)
    {
        int x, y;
        cin >> x >> y;
        cout << rmq(x, y, 1) - rmq(x, y, 0) << '\n';
    }
    return 0;
}

[蓝桥杯 2022 省 A] 选数异或 

        x ^ y = k 可以得到:y = x ^ k。所以可以让每个 x 与 k 异或得到 y 值,记录在 x 前面的最靠后的 y 值下标。那么只需要判断 [L,R] 区间内 y 的最大下标是否大于等于 L即可。

        例如:当k = 1 时,有1,2,3这组数字。1 ^ 1 = 0,2 ^ 1 = 3,3 ^ 1 = 2,那么这组数字算出y 的下标分别是 0,0,2。

        下标是零代表在 [1,i - 1] 这个区间内没有这个数( i 是当前数字下标)。当 i 等于 1 时,由 x = 1得到 y = 0,[1,0] 这个区间不合法,也没有这个数,所以下标是0;当 i 等于 2 时,由 x = 2 得到 y = 3,[1,1] 区间内没有 3,所以下标是0;当 i 等于 3 时,由 x = 3 得到 y = 2,[1,2] 区间内有 2,2 的下标在上述数组中是 2(如果区间内有多个2,应该取最后一个2的下标)。

        那么,只需要用ST表维护这些下标的最大值,就能很快的得到 [L,R] 区间内的下标最大值,再与左边界 L 比较大小即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = (1 << 20) + 5;
typedef pair<int, int>PII;
const int Max = 0x3f3f3f3f3f3f3f3f;
const int Min = -0x3f3f3f3f3f3f3f3f;
int T, n, m, k;
int f[N][30], id[N];
int lg[N];
int query(int l, int r)
{
    int s = lg[r-l+1];
    return max(f[l][s], f[r-(1<<s)+1][s]);
}
signed main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m >> k;
    for(int i = 1; i <= n; i ++)
    {
        int x;
        cin >> x;
        // 先给ST表赋值,再让id[]更新,就能实现让 i 前面的最后一个 x ^ k 的下标被存入ST表中。
        f[i][0] = id[x ^ k];
        id[x] = i;
    }
    for(int i = 2; i <= n; i ++) lg[i] = lg[i>>1] + 1;
    int tt = lg[n];
    for(int j = 1; j <= tt; j ++)
        for(int i = 1; i + (1 << j) - 1 <= n; i ++)
            f[i][j] = max(f[i][j-1], f[i+(1 << j-1)][j-1]);

    while(m --)
    {
        int l, r;
        cin >> l >> r;
        if(query(l, r) >= l) cout << "yes\n";
        else cout << "no\n";
    }
    return 0;
}

P2866 [USACO06NOV] Bad Hair Day S

        一个比较不错的题目,也可以用来练习单调栈。

        题意很直白,就是求 a[ i ] 后面在遇到大于等于它的值之前,有几个数比它小(C[ i ]),然后累加就可以了。

        建立ST表维护区间最大值之后,因为维护后的序列满足单调性,所以遍历 a[ i ] 进行二分答案,求出每个数的 C[ i ]后累加即可。

        一个数二分答案的时间复杂度是O(logn),n 个数就是O(nlogn),二分答案部分看代码注释。

        二分查找算法模板

ST表做法:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5+5;
typedef pair<int, int>PII;
const int Max = 0x3f3f3f3f3f3f3f3f;
const int Min = -0x3f3f3f3f3f3f3f3f;
int T, n, m;
int f[N][30], lg[N], a[N];
 
int query(int l, int r)
{
    if(l > r) return 0;
    int s = lg[r - l + 1];
    return max(f[l][s], f[r - (1 << s) + 1][s]);
}
 
signed main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i ++)
    {
        cin >> a[i];
        f[i][0] = a[i];
    }
    
    for(int i = 2; i <= n; i ++) lg[i] = lg[i>>1] + 1;
 
    int tt = lg[n];
    for(int j = 1; j <= tt; j ++)
        for(int i = 1; i+(1<<j)-1 <= n; i ++)
            f[i][j] = max(f[i][j-1], f[i+(1<<j-1)][j-1]);
 
    int ans = 0;
    // 对于每个 a[i] 都向后寻找有多少个数满足条件
    for(int i = 1; i <= n; i ++)
    {
        // 最初,判断 [i + 1, mid] 中最大值(max)是否大于 a[i]
        // 如果 a[i] > max:
        // 说明这个区间满足要求,可以进一步向后查找,由于满足要求所以mid可能是答案,令左边界l = mid
        // 如果 a[i] <= max:
        // 说明这个区间不满足要求,必须向前找答案,由于不满足要求所以mid 不可能是答案,令有边界r = mid - 1

        // 一开始将l的左边界定为i,而不是 i + 1,是因为如果a[i]后面没有满足条件的数字(即 i 的 C[i] == 0)
        // 那么r会一步步缩小,l不会变,循环结束后l = i,l - i = 0。可以省去特判
        int l = i, r = n;
        while(l < r)
        {
            int mid = l + r + 1 >> 1;
            if(query(i+1, mid) < a[i]) l = mid;
            else r = mid - 1;
        }
        ans += l - i;
    }
    cout << ans << '\n';
    return 0;
}

        做完后看题解,发现题解大多都是单调栈,就又写了个单调栈,因为主要是应用ST表,单调栈就不写注释了

 单调栈做法:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5+5;
typedef pair<int, int>PII;
const int Max = 0x3f3f3f3f3f3f3f3f;
const int Min = -0x3f3f3f3f3f3f3f3f;
int T, n, m;
int a[N], c[N];
stack<PII>q;

signed main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i ++) cin >> a[i];
    for(int i = 1; i <= n; i ++)
    {
        if(q.size() == 0) q.push({a[i], i});
        else
        {
            while(q.size() && q.top().first <= a[i])
            {
                c[q.top().second] += i - q.top().second - 1;
                q.pop();
            }
            q.push({a[i], i});
        }
    }
    int id = q.top().second;
    q.pop();
    while(q.size())
    {
        c[q.top().second] += id - q.top().second;
        q.pop();
    }
    int ans = 0;
    for(int i = 1; i <= n; i ++) ans += c[i];
    cout << ans << '\n';
    return 0;
}

        上面代码是把每个 C[ i ] 都求出来后累加的,但洛谷上题解写的很简单,只维护当前位置插入之前单调栈内元素的个数,累加就是答案。可以尝试自己证明

代码如下:

#include <bits/stdc++.h>
using namespace std;
long long ans;
int n, x;
stack<int> q;
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> x;
        while (!q.empty() && q.top() <= x)
            q.pop();
        ans += q.size();
        q.push(x);
    }
    cout << ans;
    return 0;
}

P7333 [JRKSJ R1] JFCA(选做)

        这个题比较难想是因为它是一个环,我们在做的时候首先需要解开环(解开环的通常做法是在原数组后面再复制一遍这个数组),但这个题比较特殊,我们要对每个数字都向两边的区间进行查找才可以,所以就可以将原本的串复制两遍,分别放到原数组的前面和后面。这样我们只用去考虑中间的那一个串,保证它向左向右查找时最多只会找到第一部分和第三部分上,既不会越界又能保证它满足环的情况 。

        这个题数据量,n方的复杂度肯定会超时,我们不能暴力去查找。它算法的复杂度最多只能是O(nlogn),当我们维护区间最大值时,它是满足单调性的,可以考虑二分答案。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e5+5;
typedef pair<int, int>PII;
const int Max = 0x3f3f3f3f3f3f3f3f;
const int Min = -0x3f3f3f3f3f3f3f3f;
int T, n, m;
int f[N][30], lo[N];

void ST()
{
    for(int j = 1; (1 << j) <= 3*n; j ++)
        for(int i = 1; i + (1 << j) - 1 <= 3*n; i ++)
            f[i][j] = max(f[i][j-1], f[i + (1 << j - 1)][j-1]);
}

int query(int l, int r)
{
    int s = lo[r - l + 1];
    return max(f[l][s], f[r - (1 << s) + 1][s]);
}

int ef(int x, int wz)
{
    int l = 1, r = n;
    while(l < r)
    {
        int mid = l + r >> 1;
        // mid代表的含义可以理解为从当前位置向左向右分别延伸mid长度
        // 也就是检查[wz - mid, wz+mid]这个区间内,除去wz本身后,是否有一个a>=x
        // 如果存在的话,mid可能是答案,所以答案在[l, mid],所以让右边界r = mid
        // 如果不存在的话,mid一定不是答案,答案在[mid+1, r],所以让左边界l = mid + 1;
        // 当l == r时, 答案值被确定,跳出循环,l(也是r)就是答案
        if(max(query(wz-mid, wz-1), query(wz+1, wz+mid)) >= x) r = mid;
        else l = mid + 1;
    }
    return l != n ? l : -1;
}

signed main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i ++)
    {
        int a;
        cin >> a;
        // ST表预处理
        f[i + 2*n][0] = f[i + n][0] = f[i][0] = a;
    }
    // 递推求i的log2是多少
    for(int i = 2; i <= 3*n; i ++) lo[i] = lo[i>>1] + 1;
    // 建ST表
    ST();
    for(int i = 1; i <= n; i ++)
    {
        int b;
        cin >> b;
        // 二分答案,开成三倍区间后,应该枚举2区间,向左右延伸1、3区间找。所以它的位置下标是i+n。
        cout << ef(b, i + n);
        if(i != n) cout << " ";
    }
    return 0;
}

P7974 [KSN2021] Delivering Balls(选做)

        这个题感觉挺难,而且难点不在ST表上,更多的是思维上的推导,可以以后基础算法学的差不多了再来做,题目描述感觉不是很清楚,可以去洛谷题解里面看一下他们画的图,就应该能明白题意了。

        对于这个题,洛谷上ST表的题解写的挺全面了,我就不再啰嗦一遍了。只简单说一下题目最后的那个式子是怎么推出来的。

        设 mxh 是区间 [l, r] 中 h[i] 的最大值,mxs 是区间 [l, r] 中 h[i] - i 的最大值, mxt 是区间 [l, r] 中 h[i] + i 的最大值。那么总体力值:

        对于行不变的体力值的计算,用起点和终点的横坐标之差(也就是水平方向上需要移动的总长度),减去两段水平方向上已经移动的长度(斜向上移动时,横纵坐标移动距离相等),就是水平方向还需要移动的距离。

        还有一个要考虑的点就是,如果起始点 s 大于终点 t 式子也是成立的。我们只需要让s1 = s,t1 = t,交换 s1 和 t1 的值,然后再去计算 mxh,mxs,mxt 就可以了。更具体点就是,s 和 t 的位置并不会影响式子的正确性,但是由于我们维护的这些区间值,是从前向后顺次维护的,而由于 s大于 t,会导致我们不能正确求出 mxh,mxs,mxt 的值,所以只需要交换一下区间范围就行了。

        比如 s = 5,t = 1。mxh 应该等于  [1,5] 这个区间内的最大值,而不是 [5,1] 的区间最大值。我们维护的是从前向后的一个区间,交换 s1、t1 后,才能得出正确的 mxh 值

        这个式子化简后就是:

mxh - 4 * h[l1] - h[r1] + 2 *(mxs + mxt)

   代码如下:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5+5;
typedef pair<int, int>PII;
const int Max = 0x3f3f3f3f3f3f3f3f;
const int Min = -0x3f3f3f3f3f3f3f3f;
int T, n, m;
int f[N][30][3], lg[N];
int h[N];
signed main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i ++)
    {
        cin >> h[i];
        f[i][0][0] = h[i];
        f[i][0][1] = h[i] - i;
        f[i][0][2] = h[i] + i;
    }
    for(int i = 2; i <= n; i ++) lg[i] = lg[i>>1] + 1;
    int tt = lg[n];
    for(int j = 1; j <= tt; j ++)
        for(int i = 1; i + (1 << j) - 1 <= n; i ++)
        {
            f[i][j][0] = max(f[i][j-1][0], f[i+(1<<j-1)][j-1][0]);
            f[i][j][1] = max(f[i][j-1][1], f[i+(1<<j-1)][j-1][1]);
            f[i][j][2] = max(f[i][j-1][2], f[i+(1<<j-1)][j-1][2]);
        }

    cin >> m;
    while(m --)
    {
        int l, r;
        cin >> l >> r;
        int l1 = l, r1 = r;
        if(l1 > r1) swap(l1, r1);
        int s = lg[r1 - l1 + 1];
        int mxh = max(f[l1][s][0], f[r1-(1<<s)+1][s][0]);
        int mxs = max(f[l1][s][1], f[r1-(1<<s)+1][s][1]);
        int mxt = max(f[l1][s][2], f[r1-(1<<s)+1][s][2]);
        cout << mxh - 4 * h[l] - h[r] + 2 *(mxs + mxt) << '\n';
    }
    return 0;
}

  • 22
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值