区间问题三部曲(1) : 在线区间最值算法
来源:互联网 发布:淘宝详情页思路 编辑:程序博客网 时间:2024/05/29 16:25
区间问题是常见的统计问题之一,广泛应用于各类问题之中。主要形式有区间最值问题,区间和问题等。而区间最值问题可以分为静态区间最值和动态区间最值。我们先看一个情景:
描述
老管家是一个聪明能干的人。他为财主工作了整整10年,财主为了让自已账目更加清楚。要求管家每天记k次账,由于管家聪明能干,因而管家总是让财主十分满意。但是由于一些人的挑拨,财主还是对管家产生了怀疑。于是他决定用一种特别的方法来判断管家的忠诚,他把每次的账目按1,2,3…编号,然后不定时的问管家问题,问题是这样的:在a到b号账中最少的一笔是多少?为了让管家没时间作假他总是一次问多个问题。
输入格式
输入中第一行有两个数m,n表示有m(m<=100000)笔账,n表示有n个问题,n<=100000。
第二行为m个数,分别是账目的钱数
后面n行分别是n个问题,每行有2个数字说明开始结束的账目编号。
输出格式
输出文件中为每个问题的答案。具体查看样例。
测试样例1
输入
10 3 1 2 3 4 5 6 7 8 9 10 2 7 3 9 1 10
输出
2 3 1
最好想的方法就是暴力法,每次询问就过一遍区间找最小值,复杂度O(n^2),对于100000数据量显然难以胜任。而如果开一个100000*100000的数组做优化空间又吃不消。所以我们需要ST算法来解决这个问题。
我们用这道题来解释ST算法的运行。
(1) 预处理’
预处理使用一个数组dp[0..n][log(n)+1]的数组,使dp[i][j]为从i开始(包括i)之后2^j个数字中的最小值。具体方法使用动态规划。
首先 dp[i][0] = a[i], 即从i开始后一个中的最小值为第i个数(自己嘛)
对于样例就是这样的
然后 对于dp[j][i],一定有dp[j][i] = min(dp[j][i-1],dp[j+2^(j-1)][i-1])
我们来证明他:dp[j][i-1]记录着j..j+2^(i-1)-1中的最小值, dp[j+2^(j-1)][i-1]则记录了j+2^(i-1)..j+2^(i-1)+2^(i-1)-1。合起来就是j..j+2^(i-1)+2^(i-1)-1即j..j+2^i-1为所求。
//无视excel公式纯属偷懒
最后 搞dp顺序: 因为计算dp[j][i] 需要 dp[j][i-1]和dp[j+2^(j-1)][i-1]。不难看出i计算dp[j][i]需要所有的dp[j][i-1],即i是单调递增的。
dp要素都齐了,给一个程序片段吧(对于原题)
void init(){ for (int i = 1; i <= 18; i++) for (int j = 1; j <= n; j++) if (j+TwoPow(i) <= n) dp[j][i] = min(dp[j][i-1],dp[j+TwoPow(i-1)][i-1]);}
其中计算2^x使用了一个小技巧:
#define TwoPow(x) ((1)<<(x))
(2)询问
算出来这些个数看起来并没什么用。然而一种很好的算法可以在O(1)效率内计算出一段区间内的最小值。先看代码
int ask(int i, int j){ int k = (int)(log(j-i+1)/log(2)); return min(dp[i][k], dp[j-TwoPow(k)+1][k]);}
看起来有些云里雾里、、但是思想非常简单。我们举例说明下:
对于2..8这个区间如何利用已经求过的数据来解答呢?关键在于将区间分为两个长度为2^x(x为自然数)的子区间,且这两个区间的并为原区间,则这两个区间的最小值为所求。即2..5, 5..8,两个区间长度都为4。2..8的最小值即为dp[2][2], dp[5][2]
再例如对于3..7, 可以分成3..6, 4..7; 对于3..8,可以分成3..6, 5..8。
一般的,对于区间a = i..j, 一定可以分成区间i..log2(|a|), j-log2(|a|)+1..j
。|a|为区间a的长度,即j-i+1。log2用换底公式可以求出来。
证明这个方法,即区间i..2^(log2(|a|)),j-log2(|a|)+1..2^log2(|a|)
的并为原区间非常简单,这里省略。
最后给出完整代码
#include <iostream>#include <cstring>#include <cmath>#include <cstdio>using namespace std;#define maxn 100010int dp[maxn][20];int n, q;#define TwoPow(x) ((1)<<(x))void init(){ for (int i = 1; i <= 18; i++) for (int j = 1; j <= n; j++) if (j+TwoPow(i) <= n) dp[j][i] = min(dp[j][i-1],dp[j+TwoPow(i-1)][i-1]);}int ask(int i, int j){ int k = (int)(log(j-i+1)/log(2)); return min(dp[i][k], dp[j-TwoPow(k)+1][k]);}int main(){ memset (dp, 127/3, sizeof(dp)); scanf("%d%d", &n, &q); for (int i = 1; i <= n; i++) scanf("%d", &dp[i][0]); init(); int i, j; while (q --> 0) { scanf("%d%d", &i, &j); printf("%d ", ask(i,j)); } return 0;}
吐槽一下c++的IO、、过于慢,居然TLE了、、、看看结果
//暴力法评测结果#0: Accepted (0ms, 768KiB)#1: Accepted (0ms, 768KiB)#2: Accepted (15ms, 800KiB)#3: Accepted (0ms, 796KiB)#4: Accepted (468ms, 1864KiB)#5: Accepted (109ms, 980KiB)#6: Accepted (265ms, 1180KiB)#7: Accepted (795ms, 2792KiB)#8: Time Limit Exceeded (1809ms, 3356KiB)#9: Accepted (842ms, 2828KiB)
//ST算法#0: Accepted (15ms, 8360KiB)#1: Accepted (0ms, 8360KiB)#2: Accepted (0ms, 8364KiB)#3: Accepted (15ms, 8360KiB)#4: Accepted (140ms, 8364KiB)#5: Accepted (15ms, 8364KiB)#6: Accepted (31ms, 8364KiB)#7: Accepted (31ms, 8360KiB)#8: Accepted (359ms, 8364KiB)#9: Accepted (406ms, 8368KiB)
别走开、第二场战斗(动态区间和)马上开始、、、虽然没什么人看。。。
- 区间问题三部曲(1) : 在线区间最值算法
- RMQ算法 (区间最值问题)
- RMQ算法:区间最值问题
- 区间最值问题
- RMQ 区间最值问题
- RMQ区间最值问题
- RMQ-区间最值问题
- 区间最值的问题
- RMQ(区间最值问题)ST算法
- RMQ之ST算法(求区间最值问题)
- POJ 3264 Balanced Lineup RMQ问题 ST算法 O(1)查找区间最值
- Splay解决区间问题[单点更新,区间最值询问]
- RMQ 区间最值查询算法
- 求解区间最值的ST算法
- RMQ算法求区间最值
- RMQ算法,求区间最值
- 区间最值查询 --RMQ算法
- st算法模板(区间最值)
- hdu 1520 树形dp
- 推导条件随机场参数估计的全过程
- uva1121 - Subsequence
- POJ 3692 Kindergarten(最大独立集)
- UI基础-01第一个iOS应用程序
- 区间问题三部曲(1) : 在线区间最值算法
- 20160212
- 计算机考研的看法
- Linux系统我最常用的20条命令
- Hibernate 与mybatis的区别
- 大数加法
- C++虚函数表详细解释及实例分析
- TensorFlow在图像识别中的应用
- 2的次幂表示 蓝桥杯