您的位置:首页 > 其它

RMQ 问题 2016.7.26

2016-07-25 21:10 267 查看
参考:《算法竞赛入门经典:训练指南》

区间最小值查询问题(Range Minimum Query,RMQ)

给出一个 n 个元素的数组 A1, A2, …, An,设计一个数据结构,支持查询操作 Query(L,R):计算 min{AL,AL+1,…,AR}

令 d(i,j) 表示从 i 开始的,长度为 2^j 的一段元素中的最小值,则可以用递推的方法计算

d(i,j):d(i,j) = min{d(i,j-1),d(i+2^(j-1),j-1)},原理如图所示



注意 2j ≤ n,因此 d 数组的元素个数不超过 nlogn,而每一项都可以在常数时间计算完毕,故总时间为 O(nlogn)

代码如下

void RMQ_Init(void)
{
for(int i = 0; i < n; ++i) {
d[i][0] = A[i];
}
for(int j = 1; (1<<j) <= n; ++j) {
for(int i = 0; i + (1<<j) - 1 < n; i++) {
d[i][j] = min(d[i][j-1], d[i + (1<<(j-1))][j-1]);
}
}
}


查询操作很简单,令 k 为满足 2k ≤ R-L+1 的最大整数,则以 L 开头、以 R 结尾的两个长度为 2k 的区间合起来即覆盖了查询区间 [L,R]

由于是取最小值,有些元素重复考虑了几遍也没关系,如图所示(注意,如果是累加,重复元素是不允许的)



int RMQ(int L, int R)
{
int k = 0;
while((1<<(k+1)) <= R-L+1) {
++k;
}
return min(d[L][k], d[R-(1<<k)+1][k]);
}






UVa 11235 Frequent values

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <queue>
#include <vector>
#include <stack>
#include <map>
#include <cmath>
#include <cctype>
#include <bitset>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef pair<int, int> Pair;

const ull mod = 1e9 + 7;
const int INF = 0x7fffffff;
const int maxn = 1e5 + 10;
int n, q;
int a[maxn];
int val[maxn], Count[maxn], num[maxn], Left[maxn], Right[maxn];
int val_Count = 0;
int Max[maxn][40];

void Init(void);

int main()
{
#ifdef __AiR_H
freopen("in.txt", "r", stdin);
#endif // __AiR_H
while (scanf("%d", &n) != EOF && n != 0) {
scanf("%d", &q);
val_Count = 0;
int t;
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
for (int i = 1; i <= n; ++i) {
if (i == 1 || a[i] != a[i-1]) {
++val_Count;
val[val_Count] = a[i];
Count[val_Count] = 1;
num[i] = val_Count;
Left[i] = i;
Right[i] = i;
t = i;
} else {
++Count[val_Count];
num[i] = val_Count;
Left[i] = t;
++Right[t];
if (i == n || a[i] != a[i+1]) {
for (int j = t+1; j <= i; ++j) {
Right[j] = Right[t];
}
}
}
}
Init();
int l, r;
while (q--) {
scanf("%d%d", &l, &r);
if (num[l] == num[r]) {
printf("%d\n", r-l+1);
} else {
int ans = max(Right[l]-l+1, r-Left[r]+1);
l = num[l] + 1, r = num[r] - 1;
if (l <= r) {
int k = 0;
while ((1 << (k+1)) <= r-l+1) {
++k;
}
int ans_t = max(Max[l][k], Max[r-(1<<k)+1][k]);
ans = max(ans, ans_t);
}
printf("%d\n", ans);
}
}
}
return 0;
}

void Init(void)
{
for (int i = 1; i <= val_Count; ++i) {
Max[i][0] = Count[i];
}
for (int j = 1; (1 << j) <= val_Count; ++j) {
for (int i = 1; i + (1 << j) - 1 <= val_Count; ++i) {
Max[i][j] = max(Max[i][j-1], Max[i + (1 << (j-1))][j-1]);
}
}
}


HDU 5726 GCD

解题思路:

RMQ + 二分

枚举左端点,随着右端点的增大,gcd 的值是不断减小的

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <queue>
#include <vector>
#include <stack>
#include <map>
#include <cmath>
#include <cctype>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;

const ull mod = 1e9 + 7;
const int INF = 0x7fffffff;
const int maxn = 1e5 + 10;
int a[maxn];
int n;
int gcd[maxn][30];
map<int, ll> Map;

void Init(void);
int Range_Gcd(int low, int pos);

int main()
{
#ifdef __AiR_H
freopen("in.txt", "r", stdin);
#endif // __AiR_H
int T;
scanf("%d", &T);
int Case = 0;
while (T--) {
Map.clear();
printf("Case #%d:\n", ++Case);
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
Init();
for (int i = 1; i <= n; ++i) {
int key = a[i], low = i, high = n;
int last_low = low;
while (1) {
if (Range_Gcd(low, high) == key) {
Map[key] += high-last_low+1;
break;
}
int low_t = low, high_t = high, mid, now_gcd, next_gcd;
while (1) {
mid = (low_t + high_t) >> 1;
now_gcd = Range_Gcd(low, mid);
if (now_gcd < key) {
high_t = mid;
} else {
next_gcd = Range_Gcd(low, mid+1);
if (next_gcd < key) {
break;
}
low_t = mid;
}
}
Map[key] += mid-last_low+1;
last_low = mid+1;
key = next_gcd;
}
}
int Q;
scanf("%d", &Q);
int l, r;
while (Q--) {
scanf("%d%d", &l, &r);
int ans = Range_Gcd(l, r);
printf("%d %I64d\n", ans, Map[ans]);
}
}
return 0;
}

void Init(void)
{
for (int i = 1; i <= n; ++i) {
gcd[i][0] = a[i];
}
for (int j = 1; (1 << j) <= n; ++j) {
for (int i = 1; i + (1 << j) - 1 <= n; ++i) {
gcd[i][j] = __gcd(gcd[i][j-1], gcd[i + (1 << (j-1))][j-1]);
}
}
}

int Range_Gcd(int low, int pos)
{
int k = 0;
while ((1 << (k+1)) <= pos-low+1) {
++k;
}
return __gcd(gcd[low][k], gcd[pos - (1 << k) + 1][k]);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: