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)
代码如下
查询操作很简单,令 k 为满足 2k ≤ R-L+1 的最大整数,则以 L 开头、以 R 结尾的两个长度为 2k 的区间合起来即覆盖了查询区间 [L,R]
由于是取最小值,有些元素重复考虑了几遍也没关系,如图所示(注意,如果是累加,重复元素是不允许的)
RMQ + 二分
枚举左端点,随着右端点的增大,gcd 的值是不断减小的
区间最小值查询问题(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]); }
相关文章推荐
- SDKD 2016 Summer Single Contest #13 .B
- 简单的jqurey用法之一个事件控制多个元素
- 录像和拍照
- 2015CCPC南阳场 D - Pick The Sticks
- next_permutation函数
- 1051. 复数乘法 (15)
- Network in Network 学习总结
- LintCode笔记(4)——二叉树的层次遍历II
- 漫谈Java虚拟机(JVM)
- WebLogic是怎样判断堵塞线程(Stuck Thread)和独占线程(Hogging Thread)的
- Ubuntu下安装setuptools
- 重置 kali Linux 的登录密码
- c++设计模式之外观模式
- POJ1006A:细节不是骗局
- VS2013下搭建SDL开发环境
- byte与char
- MySql避免重复插入记录方法(ignore,Replace,ON DUPLICATE KEY UPDATE)
- oracle的双重for循环
- SDKD 2016 Summer Single Contest #13 .A
- 近似最近邻算法总结