ST算法处理RMQ和RGQ问题的4个例题

40 篇文章 9 订阅
24 篇文章 6 订阅

之前写了一篇博客完全理解ST稀疏表在线处理RMQ问题及RGQ问题。这篇博客选了4个题来练习下如何运用 ST S T 算法。其中前两个题是模板题,大家敲一敲就好了。后两个题是 ST S T 算法的运用, ST S T 算法充当一个脚手架的作用。

ST算法处理RMQ和RGQ - 模板题

先做两个模板题,练练手。

POJ3264 - Balanced Lineup

这就个裸的RMQ问题,指定区间最大值和最小值的差值。

通过代码

#include <stdio.h>
#include <math.h>
#include <utility>

using namespace std;

#define MAX_SIZE (50000 + 10)
#define TOP 20

int arr[MAX_SIZE];
pair<int, int> dp[MAX_SIZE][(const int)TOP];
int Log2[MAX_SIZE];
int n;

int min(int a, int b)
{
    return a < b ? a : b;
}

int max(int a, int b)
{
    return a > b ? a : b;
}

pair<int, int> minmax(const pair<int, int> &a, const pair<int, int> &b)
{
    return make_pair(min(a.first, b.first), max(a.second, b.second));
}

// BF : binary_function
typedef pair<int, int> (*BF) (const pair<int, int> &, const pair<int, int> &);

void init()
{
    for (int i = 1; i < MAX_SIZE; ++i)
        Log2[i] = Log2[i >> 1] + 1;
}

void pretreat(BF func)
{
    for (int left = 0; left < n; dp[left][0] = make_pair(arr[left], arr[left]), ++left) {}
    for (int i = 1; i < Log2[n]; ++i)
        for (int left = 0; left + (1 << i) <= n; ++left)
            dp[left][i] = func(dp[left][i - 1], dp[left + (1 << (i - 1))][i - 1]);
}

// [l, r)
pair<int, int> query(int l, int r, BF func)
{
    return func(dp[l][Log2[r - l] - 1], dp[r - (1 << Log2[r - l] - 1)][Log2[r - l] - 1]);
}

int main()
{
    init();
    for (int q; EOF != scanf("%d%d", &n, &q); ) {
        for (int i = 0; i < n; scanf("%d", &arr[i++])) {}
        // arr[0] = 2; arr[1] = 1; arr[2] = 4; arr[3] = 3; arr[4] = 5;
        BF pFunc = minmax;
        pretreat(pFunc);
        for (int l, r; q--; ) {
            scanf("%d%d", &l, &r);
            pair<int, int> ret = query(l - 1, r, pFunc);
            printf("%d\n", ret.second - ret.first);
        }
    }
    return 0;
}

洛谷1890 - gcd区间

这也是个裸的RGQ问题,求指定连续区间的最大公约数。

通过代码

#include <bits/stdc++.h>

using namespace std;

#define MAX_SIZE (1000000 + 20)
#define TOP (int)(log(MAX_SIZE) / log(2)) + 1

int arr[MAX_SIZE];
int dp[MAX_SIZE][TOP];
int Log2[MAX_SIZE];
int n;

int gcd(int a, int b)
{
    return b == 0 ? a : gcd(b, a % b);
}

// BF : binary_function
typedef int (*BF) (int, int);

void init()
{
    for (int i = 1; i < MAX_SIZE; ++i)
        Log2[i] = Log2[i >> 1] + 1;
}

void pretreat(BF func)
{
    for (int left = 0; left < n; dp[left][0] = arr[left], ++left) {}
    for (int i = 1; i < Log2[n]; ++i)
        for (int left = 0; left + (1 << i) <= n; ++left)
            dp[left][i] = func(dp[left][i - 1], dp[left + (1 << (i - 1))][i - 1]);
}

// [l, r)
int query(int l, int r, BF func)
{
    return func(dp[l][Log2[r - l] - 1], dp[r - (1 << Log2[r - l] - 1)][Log2[r - l] - 1]);
}

int main()
{
    init();
    for (int m; EOF != scanf("%d%d", &n, &m); ) {
        for (int i = 0; i < n; scanf("%d", &arr[i++])) {}
        BF pFunc = gcd;
        pretreat(pFunc);
        for (int l, r; m--; ) {
            scanf("%d%d", &l, &r);
            printf("%d\n", query(l - 1, r, pFunc));
        }
    }
    return 0;
}

ST算法简单应用

POJ3368 - Frequent values

题目大意

正如标题中说的,这题问的是指定区间中出现最频繁的数字,且给出的序列是不减的。

解析

注意序列是不减的,这也就意味着,最频繁出现的数字一定是连续的,因此我们可以把连续的数字集中起来,然后这个问题就变成了RMQ问题了(求最大值)。但是这样有个问题,给出的区间可能不完全包含连续的数字(出现在左右端点)。那么这个时候怎么办呢,可以按下图处理:

这里写图片描述

以下排数字用 ST S T 算法求RMQ。这样子对于右端点是没问题的,但是对于左端点不可行。那怎么办呢?我们可以把左边砍掉一部分,使得区间的左端点刚好位于连续数字的开始处mp[a[i]] = i,这样就可以了。

通过代码

#include <stdio.h>
#include <utility>
#include <map>

using namespace std;

#define MAX_SIZE (100000 + 10)
#define TOP 20

int a[MAX_SIZE];
int arr[MAX_SIZE];
int dp[MAX_SIZE][TOP];
int Log2[MAX_SIZE];
int n;

int max(int a, int b)
{
    return a > b ? a : b;
}

// BF : binary_function
typedef int (*BF) (int, int);

void init()
{
    for (int i = 1; i < MAX_SIZE; ++i)
        Log2[i] = Log2[i >> 1] + 1;
}

void pretreat(BF func)
{
    for (int left = 0; left < n; dp[left][0] = arr[left], ++left) {}
    for (int i = 1; i < Log2[n]; ++i)
        for (int left = 0; left + (1 << i) <= n; ++left)
            dp[left][i] = func(dp[left][i - 1], dp[left + (1 << (i - 1))][i - 1]);
}

// [l, r)
int query(int l, int r, BF func)
{
    return func(dp[l][Log2[r - l] - 1], dp[r - (1 << Log2[r - l] - 1)][Log2[r - l] - 1]);
}

int main()
{
    init();
    for (int q; EOF != scanf("%d", &n) && n; ) {
        scanf("%d", &q);
        map<int, int> mp;
        for (int i = 0; i < n; ++i) {
            scanf("%d", &a[i]);
            mp[a[i]] = i;
            arr[i] = i == 0 ? 1 : a[i] == a[i - 1] ? arr[i - 1] + 1 : 1;
        }

        BF pFunc = max;
        pretreat(pFunc);
        for (int l, r; q--; ) {
            scanf("%d%d", &l, &r);
            int src = mp[a[l - 1]];         // last-index occur
            if (src >= r - 1)
                printf("%d", r - l + 1);
            else
                // [l - 1, src]...[src + 1, r)
                printf("%d", pFunc(src - (l - 1) + 1, query(src + 1, r, pFunc)));
            puts("");
        }
    }
    return 0;
}

HDU5726 - GCD

题目大意

求指定区间的最大公约数和区间[1, n]中有多少个区间的公约数等于你求的这个公约数。

解析

第一问就是裸的RMQ问题,重点在第二问。

区间[1, n],这意味着我们可以预处理;

同时,要意识到,随着数字的增多,最大公约数一定是不增的,什么意思呢?就是gcd(3, 6)一定大于等于gcd(3, 6, 9)gcd(3, 6, 9, 7)的,这也就是说,如果我们固定区间的左端点,增加区间的右端点,得到的区间最大公约数一定是不降的(通过草稿可以发现,这个区间最大公约数序列相同的元素很多,且不同的元素之间差值很大);

只要你发现了以上加粗字体的规律,那么这个题的具体做法就出来。

枚举区间的左端点,然后二分其右端点,右端点就是区间最大公约数序列的间断点,然后维护个数就行了(map[gcd[l, l + 1, ..., r)] = r - l)。

通过代码

#include <bits/stdc++.h>

using namespace std;

#define MAX_SIZE (100000 + 10)
#define TOP (int)(log(MAX_SIZE) / log(2)) + 1

typedef long long LL;

int arr[MAX_SIZE];
int dp[MAX_SIZE][TOP];
int Log2[MAX_SIZE];
int n;

int gcd(int a, int b)
{
    return b == 0 ? a : gcd(b, a % b);
}

// BF : binary_function
typedef int (*BF) (int, int);

void init()
{
    for (int i = 1; i < MAX_SIZE; ++i)
        Log2[i] = Log2[i >> 1] + 1;
}

void pretreat(BF func)
{
    for (int left = 0; left < n; dp[left][0] = arr[left], ++left) {}
    for (int i = 1; i < Log2[n]; ++i)
        for (int left = 0; left + (1 << i) <= n; ++left)
            dp[left][i] = func(dp[left][i - 1], dp[left + (1 << (i - 1))][i - 1]);
}

// [l, r)
int query(int l, int r, BF func)
{
    return func(dp[l][Log2[r - l] - 1], dp[r - (1 << Log2[r - l] - 1)][Log2[r - l] - 1]);
}

int bFind(int src, int l, int r, int tag)
{
    while (l < r) {
        int mid = (l + r) >> 1;
        if (query(src, mid + 1, gcd) == tag)
            l = mid + 1;
        else
            r = mid;
    }
    return l;
}

int main()
{
    init();
    int T, K = 1, q;
    for (scanf("%d", &T); T--; ++K) {
        scanf("%d", &n);
        for (int i = 0; i < n; scanf("%d", &arr[i++])) {}
        scanf("%d", &q);
        BF pFunc = gcd;
        pretreat(pFunc);

        printf("Case #%d:\n", K);

        int ans1;
        map<int, LL> ans2;

        for (int src = 0; src < n; ++src) {
            for (int left = src, right; left < n; left = right) {
                int g = query(src, left + 1, pFunc);
                right = bFind(src, left, n, g);
                ans2[g] += right - left;
            }
        }

        for (int l, r; q--; ) {
            scanf("%d%d", &l, &r);
            ans1 = query(l - 1, r, pFunc);
            printf("%d %lld\n", ans1, ans2[ans1]);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值