RMQ_Sparse Table & Segment Tree

来源:互联网 发布:淘宝入驻费用标准 编辑:程序博客网 时间:2024/06/11 08:03

做了hihoCoder上的一道题,折腾了俩小时,也是醉了哭


TLE:

一开始用java做的,TLE,看到有可能是因为scanner的原因,详见http://www.cpe.ku.ac.th/~jim/java-io.html(赞),照着改了一下,降到7000+降到6000+(单位毫秒)。

从java这边没什么头绪,于是决定用C++玩一次,算法是一模一样的,直接降到1000+。

把cin换成scanf,把cout换成printf,O了(我倒)。


Sparse Table算法:

参考http://www.cppblog.com/reiks/archive/2009/08/28/94629.aspx,摘抄精华部分

RMQ(Range Minimum/Maximum Query)问题:

  RMQ问题是求给定区间中的最值问题。当然,最简单的算法是O(n)的,但是对于查询次数很多(设置多大100万次),O(n)的算法效率不够。可以用线段树将算法优化到O(logn)(在线段树中保存线段的最值)。不过,Sparse_Table算法才是最好的:它可以在O(nlogn)的预处理以后实现O(1)的查询效率。下面把Sparse Table算法分成预处理和查询两部分来说明(以求最小值为例)。

 

预处理:

预处理使用DP的思想,f(i, j)表示[i, i+2^j - 1]区间中的最小值,我们可以开辟一个数组专门来保存f(i, j)的值。

例如,f(0, 0)表示[0,0]之间的最小值,就是num[0], f(0, 2)表示[0, 3]之间的最小值, f(2, 4)表示[2, 17]之间的最小值

注意, 因为f(i, j)可以由f(i, j - 1)和f(i+2^(j-1), j-1)导出, 而递推的初值(所有的f(i, 0) = i)都是已知的

所以我们可以采用自底向上的算法递推地给出所有符合条件的f(i, j)的值。

 

查询:

假设要查询从m到n这一段的最小值, 那么我们先求出一个最大的k, 使得k满足2^k <= (n - m + 1).

于是我们就可以把[m, n]分成两个(部分重叠的)长度为2^k的区间: [m, m+2^k-1], [n-2^k+1, n];

而我们之前已经求出了f(m, k)为[m, m+2^k-1]的最小值, f(n-2^k+1, k)为[n-2^k+1, n]的最小值

我们只要返回其中更小的那个, 就是我们想要的答案, 这个算法的时间复杂度是O(1)的.

例如, rmq(0, 11) = min(f(0, 3), f(4, 3))


代码:C++

#include <iostream>
#include <cmath>
#include <stdio.h>
using namespace std;


int *a;
int N=0;
int column=0;
int **storage;
int query( int li, int ri );
void preCalculate();


int main(){
scanf("%d",&N);
a= new int[N];
for( int i=0; i<N; i++ )
scanf("%d",&a[i]);
column = (int) ( log((double)N) / log(2.0) )+1;
storage = new int*[N];
for( int i=0; i<N; i++ )
storage[i] = new int[column];


preCalculate();


int Q=0,li=0,ri=0 ;
scanf("%d",&Q);
for( int i=0; i<Q; i++ ){
scanf("%d",&li);
scanf("%d",&ri);
printf("%d\n", query(li,ri) );
}


delete[] a;
for( int i=0; i<N;i++ )
delete[] storage[i];
delete[] storage;


return 0;
}


void print(){
for( int i=0; i<N; i++ ){
for( int j=0; j<column; j++ )
cout<<storage[i][j]<<" ";
cout<<endl;
}
}


void preCalculate(){
for( int i=0; i<N; i++ ){
storage[i][0]=a[i];
}
for( int j=1; j<column; j++ ){
int subLength =(int) pow(2.0, j-1);
int rightBoundary = N-2*subLength+1;
for( int i=0; i<rightBoundary; i++ ){
storage[i][j] = min( storage[i][j-1], storage[i+subLength][j-1]); 
}
}
//print();
}


int query( int li, int ri ){
if( li==ri )
return a[--li];
li--;
ri--;
int length = ri-li+1;
int t =(int)(log((double)length)/log(2.0));
int subLength =(int) pow(2.0, t);
if( length==subLength )
return storage[li][t];
else
return min( storage[li][t], storage[ri-subLength+1][t] );


}


Segment Tree算法

参见http://hi.baidu.com/alpc62/item/be736a33a8864789f4e4ad18,http://blog.csdn.net/ljsspace/article/details/6654853

#include <iostream>
#include <stdio.h>
#include <cmath>
#include <string.h>
using namespace std;


int N=0;
int *weight;
int height=0;
int treeSize=0;
int *segmentTree;
int query(int li, int ri);
int query_recursively(int li, int ri, int lb, int rb, int node);
void preCalculate( int l, int r, int node );
void printSegmentTree();


int main(){
scanf("%d",&N);
weight = new int[N];
for( int i=0; i<N; i++ ){
scanf("%d",&weight[i]);
}
height = (int)(log((double)N)/log(2.0))+1;
treeSize = (int)pow(2.0, height+1 )-1;
segmentTree = new int[treeSize];
memset(segmentTree,0,sizeof(int)*treeSize);
preCalculate(0,N-1,0);
//printSegmentTree();


int Q = 0, li=0, ri=0;
scanf("%d",&Q);
for( int i=0; i<Q; i++ ){
scanf("%d",&li);
scanf("%d",&ri);
printf("%d\n", query(li,ri) );
}


delete[] weight;
delete[] segmentTree;
return 0;

}


void printSegmentTree(){
int count=0;
for( int i=0; i<=height; i++ ){
int total = (int)pow(2.0,i);
for( int j=0; j<total; j++,count++ ){
cout<<segmentTree[count]<<" ";
}
cout<<endl;
}
}




int query(int li, int ri){
/*
if( li<=0 || ri>N || ri<li )
{
cerr<<"Query failure: Boundary Exeption. left boundary="<<li<<" right boudary="<<ri<<endl;
throw new exception();
}
*/
if( --li == --ri )
return weight[li];
return query_recursively(li,ri,0,N-1,0);
}


//[li,ri] stands for query boudary
//[lb,rb] stands for search scope
//node stands for current search node
int query_recursively(int li, int ri, int lb, int rb, int node){
/*
if( li>rb || ri<lb || ri<li || rb<lb )
{
cerr<<"Query failure: Boundary Exeption. query left boundary="<<li
<<" query right boudary="<<ri
<<" search left boudary="<<lb
<<" search right boudary="<<rb<<endl;
throw new exception();
}
*/
if( li==lb && ri==rb )
return segmentTree[node];
int mid = ((rb-lb)>>1)+lb;
if( li> mid )
return query_recursively(li,ri,mid+1,rb,2*node+2);
else if( ri<=mid )
return query_recursively(li,ri,lb,mid,2*node+1);
else
return min( query_recursively(li,mid,lb,mid,2*node+1), 
query_recursively(mid+1,ri,mid+1,rb,2*node+2) );


}


//init the segment tree
void preCalculate( int l, int r, int node ){
/*
if( r<l ){
cerr<<"Initial segment tree failure: Boundary Exeption. left boundary="<<l<<" right boudary="<<r<<endl;
throw new exception();
}
*/
if( l==r ){
segmentTree[node] = weight[l];
return;
}
int mid = ((r-l)>>1)+l;
preCalculate(l,mid,node*2+1);
preCalculate(mid+1,r,node*2+2);
segmentTree[node] = min( segmentTree[node*2+1], segmentTree[node*2+2]);
}


比较:

(1)用线段树解决RMQ问题的复杂度为<O(n),O(logn)>,用sparce table复杂度为<O(logn),O(1)>

(2)线段树的好处是方便进行增加和修改,比如本题中物品重量变化的话用线段树可以较为容易地修改树的存储

(3)对本题而言,sparsetable查找了两个可能重叠的域,而线段树每次查找都为不重叠的域。例如对于求和应用时,sparsetable可能就不是那么合适了。


奇怪错误:

(1)遇到Run time error: 括号加的不对敲打。代码规范很重要,该加括号加括号。

(2)Compile error:缺少头文件。比如memset需要<string.h>,scanf需要<stdio.h>等

(3)TLE:算法复杂度太高,I/O耗时等

0 0
原创粉丝点击