之前写了一篇博客完全理解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;
}