题目链接:http://poj.org/problem?id=3264
参考博客链接:https://blog.csdn.net/qq_31759205/article/details/75008659
理解:题意求给定区域内最值之差。数据太大,暴力是行不通的,首先想到的是线段树,但RMQ实行和理解起来更简洁,就以新学的算法来写啦,所以等下细记录RMQ代码,略记录线段树代码,反正我的博客我做主,也就我一个人看。
以下是线段树解法:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #define maxn 50005 5 int a[maxn],treemax[maxn<<2],treemin[maxn<<2];//最大值和最小值树; 6 using namespace std; 7 8 int max(int a,int b) 9 { 10 return a>b?a:b; 11 } 12 13 int min(int a,int b) 14 { 15 return a>b?b:a; 16 } 17 18 void build(int l,int r,int n) 19 { 20 if(l==r) 21 { 22 treemax[n]=a[l]; 23 treemin[n]=a[l]; 24 return; 25 } 26 int mid=(l+r)>>1; 27 build(l,mid,n<<1); 28 build(mid+1,r,n<<1|1); 29 treemax[n]=max(treemax[n<<1],treemax[n<<1|1]); 30 treemin[n]=min(treemin[n<<1],treemin[n<<1|1]); 31 }//同时建立最大值最小值树; 32 33 int searchmax(int L,int R,int l,int r,int n) 34 { 35 if(l>=L&&r<=R) 36 return treemax[n]; 37 int mid=(l+r)>>1,ans=-1; 38 if(L<=mid) 39 ans=max(ans,searchmax(L,R,l,mid,n<<1)); 40 if(mid<R) 41 ans=max(ans,searchmax(L,R,mid+1,r,n<<1|1)); 42 return ans; 43 } 44 45 int searchmin(int L,int R,int l,int r,int n) 46 { 47 if(l>=L&&r<=R) 48 return treemin[n]; 49 int mid=(l+r)>>1,ans=5000000; 50 if(L<=mid) 51 ans=min(ans,searchmin(L,R,l,mid,n<<1)); 52 if(mid<R) 53 ans=min(ans,searchmin(L,R,mid+1,r,n<<1|1)); 54 return ans; 55 }//最大值树和最小值树就注意ans的初始化了; 56 57 int main() 58 { 59 int m,n; 60 while(~scanf("%d%d",&m,&n)) 61 { 62 int l,f; 63 for(int i=1;i<=m;i++) 64 scanf("%d",&a[i]); 65 build(1,m,1); 66 while(n--) 67 { 68 scanf("%d%d",&l,&f); 69 printf("%d ",searchmax(l,f,1,m,1)-searchmin(l,f,1,m,1)); 70 } 71 } 72 return 0; 73 }
对于RMQ,用了dp,用于解决区间最值问题很不错吧,只能感叹发明这算法的人厉害了,dp[i][j]的意思是对于第i个数,往后2的j次方个数里的最值是多少。先理清思路吧:
1:状态转移方程可运用性:F[i, j]=max(F[i,j-1], F[i + 2^(j-1),j-1]);因为对于每个dp所储存2^j范围的最值,都可以通过比较两段连续2^j-1范围的最值得到,第一段为以数i,范围j-1,即F[i,j-1],另一段为以数i+2^(j-1)(原数加原范围的一半),范围j-1,即F[i + 2^(j-1),j-1];
2:查找可确定性:dp范围虽然是2^n,但可以实现任意区间的比较,利用了区间重复比较(即任意区间的表示都可以利用两个区间的并集来代替):比如区间(1,5)可以由(1,4)和(2,5)两个区间代替,这里就涉及到了代码中k值的计算(2^k因小于等于区间长度)。
现在原理弄明白了(参考博客里有大牛解释),就上代码吧:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #define maxn 50005 6 int a[maxn],dpmax[maxn][20],dpmin[maxn][20];//最大值和最小值背包; 7 using namespace std; 8 int max(int a,int b) 9 { 10 return a>b?a:b; 11 } 12 13 int min(int a,int b) 14 { 15 return a>b?b:a; 16 } 17 18 int search(int index,int left,int right)//index为1,表示要返回区间最大值,其他为最小值; 19 { 20 int k=0; 21 while((1<<(k+1))<=right-left+1) k++;//求k值,即所求的的范围2^k所不能超过,这样才能通过用两个比范围 小一级的范围代替; 22 if(index==1) 23 return max(dpmax[left][k],dpmax[right-(1<<k)+1][k]); 24 else 25 return min(dpmin[left][k],dpmin[right-(1<<k)+1][k]);//返回两个范围的最值; 26 } 27 28 int main() 29 { 30 int m,n; 31 while(~scanf("%d%d",&m,&n)) 32 { 33 int left,right; 34 for(int i=1;i<=m;i++) 35 { 36 scanf("%d",&a[i]); 37 dpmax[i][0]=a[i]; 38 dpmin[i][0]=a[i];//初始化长度为1的背包 39 } 40 for(int i=1;(1<<i)<=m;i++)//i代表长度,即为2^i长度 ,长度比总长度或等于总长度结束,从2长度开始; 41 for(int j=1;j+(1<<i)-1<=m;j++) //j代表从第j个数开始 (因为长度包括本身数) 42 { 43 dpmax[j][i]=max(dpmax[j][i-1],dpmax[j+(1<<(i-1))][i-1]); 44 dpmin[j][i]=min(dpmin[j][i-1],dpmin[j+(1<<(i-1))][i-1]); 45 }//同时填满最大最小值背包; 46 for(int i=0;i<n;i++) 47 { 48 scanf("%d%d",&left,&right); 49 printf("%d ",search(1,left,right)-search(0,left,right)); 50 } 51 } 52 return 0; 53 } 54 //笔记:x<<y代表x乘以2^y;
总的就这样了;这题测试数据量很大,用cin会超时,还是老老实实用sp吧。