POJ-3264 RMQ

题目链接: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\n",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\n",search(1,left,right)-search(0,left,right));
50         }
51     }
52     return 0;
53 }
54 //笔记:x<<y代表x乘以2^y;

总的就这样了;这题测试数据量很大,用cin会超时,还是老老实实用sp吧。

posted @ 2018-04-10 21:41  超人不穿内裤  阅读(137)  评论(0编辑  收藏  举报