dp+离散(RMQ)

来源:互联网 发布:巨龙之巢网络波动 编辑:程序博客网 时间:2024/06/05 15:11

一、RMQ问题描述

RMQ (Range Minimum/Maximum Query)问题是指:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在[i,j]里的最小(大)值,也就是说,RMQ问题是指求区间最值的问题

ST算法(Sparse Table):它是一种动态规划的方法。 
以最小值为例。a为所寻找的数组. 
用一个二维数组dp(i,j)记录区间[i,i+2^j-1](持续2^j个)区间中的最小值。其中dp[i,0] = a[i]; 所以,对于任意的一组(i,j),dp(i,j) = min{dp(i,j-1),dp(i+2^(j-1),j-1)}来使用动态规划计算出来。 
这个算法的高明之处不是在于这个动态规划的建立,而是它的查询:它的查询效率是O(log(j-i)). 假设我们要求区间[m,n]中a的最小值,找到一个数k使得2^k<n-m+1)并且2^(k+1)>=n-m+1
这样,可以把这个区间分成两个部分:[m,m+2^k-1]和[n-2^k+1,n].这两个部分要一定保证完全覆盖这个区间,我们发现,这两个区间是已经初始化好的. 
前面的区间是dp(m,k),后面的区间是dp(n-2^k+1,k). 
这样,只要看这两个区间的最小值,就可以知道整个区间的最小值!

二、RMQ的应用

1、poj1207

/*裸的RMQ*/#include <iostream>#include <cstdio>#include <cstring>using namespace std;#define max(a,b) ((a)>(b)?(a):(b))int dat[10002];int dp[10002][18];int get_dat(int n){int cnt=1;while(n!=1){if(n&1){n=3*n+1;}else{n/=2;}++cnt;}return cnt;}void make_max_rmq(){for(int j=1;(1<<j)<=10000;++j){for(int i=1;i+(1<<j)-1<=10000;++i){dp[i][j]=max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);}}}int get_max_rmq(int a,int b){int k=0;while(a+(1<<k)<b-(1<<k)+1)++k;return max(dp[a][k],dp[b-(1<<k)+1][k]);}int main(){for(int i=1;i<=10000;++i){dat[i]=get_dat(i);dp[i][0]=dat[i];}int l,h,a,b;make_max_rmq();while(~scanf("%d%d",&a,&b)){l=a<b?a:b;h=a>b?a:b;int ans=get_max_rmq(l,h);printf("%d %d %d\n",a,b,ans);}return 0;}


 

2、poj3264

/*n个数,求i,j的最大值和最小值之差 */#include <iostream>#include <cstdio>#include <cstring>using namespace std;#define max(a,b) ((a)>(b)?(a):(b))#define min(a,b) ((a)<(b)?(a):(b))int m,n;int dat[50002];int dpmax[50002][18];int dpmin[50002][18];void make_max_rmq(){for(int i=1;i<=n;++i){dpmax[i][0]=dat[i];}for(int j=1;(1<<j)<=n;++j){for(int i=1;i+(1<<j)-1<=n;++i){dpmax[i][j]=max(dpmax[i][j-1],dpmax[i+(1<<(j-1))][j-1]);}}}void make_min_rmq(){for(int i=1;i<=n;++i){dpmin[i][0]=dat[i];}for(int j=1;(1<<j)<=n;++j){for(int i=1;i+(1<<j)-1<=n;++i){dpmin[i][j]=min(dpmin[i][j-1],dpmin[i+(1<<(j-1))][j-1]);}}}int get_max_rmq(int a,int b){int k=0;while(a+(1<<k)<b-(1<<k)+1)++k;return max(dpmax[a][k],dpmax[b-(1<<k)+1][k]);}int get_min_rmq(int a,int b){int k=0;while(a+(1<<k)<b-(1<<k)+1)++k;return min(dpmin[a][k],dpmin[b-(1<<k)+1][k]);}int main(){int a,b;while(~scanf("%d%d",&n,&m)){for(int i=1;i<=n;++i){scanf("%d",&dat[i]);}make_max_rmq();make_min_rmq();while(m--){scanf("%d%d",&a,&b);int ans=get_max_rmq(a,b)-get_min_rmq(a,b);printf("%d\n",ans);}}return 0;}


 

3、poj3368

/* * 有n个数升序排列,输出i,j的最大的众数 */#include <iostream>#include <cstdio>#include <cstring>#include <cmath>using namespace std;#define max(a,b) ((a)>(b)?(a):(b))#define N 100002int n,m;int rank[N];int dat[N];int dp[N][18];int rear[N];int head[N];void make_max_rmq(){for(int i=1;i<=n;++i){dp[i][0]=rank[i];}for(int j=1;j<=(int)(log((double)n)/log(2.0));++j){for(int i=1;i+(1<<j)-1<=n;++i){dp[i][j]=max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);}}}int get_max_rmq(int a,int b){int k=0;while(a+(1<<k)<b-(1<<k)+1)++k;  return max(dp[a][k],dp[b-(1<<k)+1][k]);}int main(){int a,b;while(~scanf("%d",&n)){if(!n)break;scanf("%d",&m);int cnt=0;memset(head,0,sizeof(head));memset(rear,0,sizeof(rear));head[0]=1;for(int i=1;i<=n;++i){scanf("%d",&dat[i]);if(dat[i]==dat[i-1]||i==1){rank[i]=++cnt;head[i]=head[i-1];}else{head[i]=i;rear[head[i-1]]=i-1;rank[i]=1;cnt=1;}}make_max_rmq();while(m--){scanf("%d%d",&a,&b);int ans;if(dat[a]==dat[b]){ans=b-a+1;}else if(rear[head[a]]+1==head[b]){ans=max(rear[head[a]]-a+1,b-head[b]+1);}else{ans=max(rear[head[a]]-a+1,b-head[b]+1);ans=max(ans,get_max_rmq(rear[head[a]]+1,head[b]-1));}printf("%d\n",ans);}}return 0;}


 

4、poj2109 二维RMQ

/* 求子矩阵的最大值与最小值 */#include <iostream>#include <cstdio>#include <cstring>using namespace std;#define max(a,b) ((a)>(b)?(a):(b))#define min(a,b) ((a)<(b)?(a):(b))#define inf 0x7ffffffint dpmax[252][252][8];int dpmin[252][252][8];int dat[252][252];int n,b,k;void make_rmq2(){for(int k=1;(1<<k)<=n;++k){for(int i=1;i<=n;++i){for(int j=1;j<=n;++j){dpmax[i][j][k]=dpmax[i][j][k-1];if(i+(1<<(k-1))<=n)dpmax[i][j][k]=max(dpmax[i][j][k],dpmax[i+(1<<(k-1))][j][k-1]);if(j+(1<<(k-1))<=n)dpmax[i][j][k]=max(dpmax[i][j][k],dpmax[i][j+(1<<(k-1))][k-1]);if((i+(1<<(k-1))<=n)&&(j+(1<<(k-1))<=n))dpmax[i][j][k]=max(dpmax[i][j][k],dpmax[i+(1<<(k-1))][j+(1<<(k-1))][k-1]);dpmin[i][j][k]=dpmin[i][j][k-1];if(i+(1<<(k-1))<=n)dpmin[i][j][k]=min(dpmin[i][j][k],dpmin[i+(1<<(k-1))][j][k-1]);if(j+(1<<(k-1))<=n)dpmin[i][j][k]=min(dpmin[i][j][k],dpmin[i][j+(1<<(k-1))][k-1]);if((i+(1<<(k-1))<=n)&&(j+(1<<(k-1))<=n))dpmin[i][j][k]=min(dpmin[i][j][k],dpmin[i+(1<<(k-1))][j+(1<<(k-1))][k-1]);}}}}int get_max_rmq2(int x,int y,int k){int r=dpmax[x][y][k];if(x+b-(1<<k)<=n)r=max(r,dpmax[x+b-(1<<k)][y][k]);if(y+b-(1<<k)<=n)r=max(r,dpmax[x][y+b-(1<<k)][k]);if((x+b-(1<<k)<=n)&&(y+b-(1<<k)<=n))r=max(r,dpmax[x+b-(1<<k)][y+b-(1<<k)][k]);return r;}int get_min_rmq2(int x,int y,int k){int r=dpmin[x][y][k];if(x+b-(1<<k)<=n)r=min(r,dpmin[x+b-(1<<k)][y][k]);if(y+b-(1<<k)<=n)r=min(r,dpmin[x][y+b-(1<<k)][k]);if((x+b-(1<<k)<=n)&&(y+b-(1<<k)<=n))r=min(r,dpmin[x+b-(1<<k)][y+b-(1<<k)][k]);return r;}int main(){int x,y;while(~scanf("%d%d%d",&n,&b,&k)){for(int i=1;i<=n;++i){for(int j=1;j<=n;++j){scanf("%d",&dat[i][j]);dpmax[i][j][0]=dpmin[i][j][0]=dat[i][j];}}int p=0;while((1<<(p+1))<=b){++p;}make_rmq2();while(k--){scanf("%d%d",&x,&y);int ans=get_max_rmq2(x,y,p)-get_min_rmq2(x,y,p);printf("%d\n",ans);}}return 0;}


 

5、Poj2452 RMQ+两次二分

/*题意:有一串N个不同数字的序列,求出一个最长的连续子序列si~sj,满足si是最小数,sj是最大数。思路:首先预处理,对于每个si,找出以它为起点后面最大数的位置rear[i].然后二分找到以si为最小值的区间,然后在这个区间里面二分寻找最大值的位置j,j-i的最大值即为所求*/#include <iostream>#include <cstdio>#include <cstring>using namespace std;#define min(a,b) ((a)<(b)?(a):(b))#define max(a,b) ((a)>(b)?(a):(b))。const int N=5e4+2;int n;int dat[N];int dpmax[N][30];int dpmin[N][30];int rear[N];void make_rmq(){for(int j=1;(1<<j)<=n;++j){for(int i=1;i+(1<<j)-1<=n;++i){dpmax[i][j]=max(dpmax[i][j-1],dpmax[i+(1<<(j-1))][j-1]);dpmin[i][j]=min(dpmin[i][j-1],dpmin[i+(1<<(j-1))][j-1]);}}}int get_max_rmq(int a,int b){int k=0;while(a+(1<<k)<b-(1<<k)+1)++k;return max(dpmax[a][k],dpmax[b-(1<<k)+1][k]);}int get_min_rmq(int a,int b){int k=0;while(a+(1<<k)<b-(1<<k)+1)++k;return min(dpmin[a][k],dpmin[b-(1<<k)+1][k]);}int main(){while(~scanf("%d",&n)){for(int i=1;i<=n;++i){scanf("%d",&dat[i]);dpmin[i][0]=dpmax[i][0]=dat[i];}int t=n;rear[n]=n;for(int i=n-1;i>=1;--i){if(dat[i]<dat[t]){rear[i]=t;}else{rear[i]=i;t=i;}}for(int i=1;i<=n;++i){}int ans=-1;make_rmq();for(int i=1;i<=n;++i){int low=i,high=rear[i],mid,t=-1;if(get_min_rmq(i,high)==dat[i]){t=max(t,high);}else{while(low<high){if(get_min_rmq(i,low)==dat[i]){t=max(t,low);}if(get_min_rmq(i,high)==dat[i]){t=max(t,high);}mid=(low+high)/2;if(get_min_rmq(i,mid)==dat[i]){t=max(t,mid);low=mid+1;}else{high=mid-1;}}}high=t;low=i;int w=get_max_rmq(low,high);if(get_max_rmq(i,high)==dat[high]){ans=max(ans,high-i);}else{while(low<high){if(dat[low]==w){ans=max(ans,low-i);}if(dat[high]==w){ans=max(ans,high-i);}mid=(low+high)/2;if(get_max_rmq(i,mid)<w){low=mid+1;}else{high=mid-1;if(dat[mid]==w)ans=max(ans,mid-i);}}}}if(ans==0)ans=-1;printf("%d\n",ans);}return 0;}


 

 

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 拉杆轮子坏了怎么办 外拉杆球头旷了怎么办 古早味蛋糕回缩怎么办 孩子迷上嘻哈怎么办 吃扇贝过敏了怎么办 海鲜和杨梅一起怎么办 我买东西没眼光怎么办 兔子身上有跳蚤怎么办 物业不提供发票怎么办 购房发票 不给怎么办 新锐大灯光散不聚怎么办 网络碰到垃圾人怎么办 有人侮辱我家人怎么办 亲人欠网贷还不上现在病重怎么办 亲人欠网贷现在病重怎么办 小孩子大嘴巴病怎么办 小孩子有心胀病怎么办 小孩子有胃痛病怎么办 大修基金用完了怎么办 基金买错了怎么办 基金公司倒闭了怎么办 桔子分期有风险怎么办 想买vivox21没钱怎么办 中介收钱不办事怎么办 手机应用程序几个运行怎么办 网上彩票是骗局怎么办 辞职后不想辞职怎么办 口头辞职后悔了怎么办 纸质承兑丢了怎么办 天天利财倒闭怎么办 网络理财平台诈骗怎么办 银行理财产品到期后怎么办 30岁没工作怎么办 理财投资被骗了怎么办 支付宝不想收钱怎么办 信融财富逾期怎么办 拼多多砍价上限怎么办 肺功能只有50%怎么办 办小商店营业执照怎么办 悉尼退税未到账怎么办 筋斗云钱包余额怎么办