【专题】用ST表解决RMQ刷题总结

【专题】用ST表解决RMQ刷题总结

看了一下上次写博客居然是好久以前的事了(我真是老懒狗了
开门见山,直接放专题链接和代码

kuangbin rmq专题

这个contest里面一共十道题但是实际上有2道dp的题混入其中,还有一道水题不用rmq就能做(不过做上头了也能强行rmq),还有一道HYSBZ上面的题因为懒就没有做。。。 然后就剩下6道题了,一道一道按难易和类型(一维rmq/二维rmq)摆代码

一维rmq(4道):

POJ3264

题意略。。。就是rmq板子题,有手就行

//POJ3264
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
const int maxn=5e4+5;

int mx[maxn][17],mi[maxn][17];
int a[maxn],n,q;

void rmqinit(){
	for(int i=1;i<=n;i++) mx[i][0]=mi[i][0]=a[i];
	for(int j=1;j<=16;j++)
		for(int i=1;i+(1<<j)-1<=n;i++)
			mx[i][j]=max(mx[i][j-1],mx[i+(1<<(j-1))][j-1]),
			mi[i][j]=min(mi[i][j-1],mi[i+(1<<(j-1))][j-1]);
}

int rmq(int l,int r){
	int d=log2(r-l+1);
	int maxx=max(mx[l][d],mx[r-(1<<d)+1][d]);
	int minn=min(mi[l][d],mi[r-(1<<d)+1][d]);
	return maxx-minn;
}

int main()
{
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	rmqinit();
	while(q--){
		int l,r;
		scanf("%d%d",&l,&r);
		printf("%d\n",rmq(l,r));
	}
} 

POJ3368

题意:给你一个数列,有n个数a1,a2,….,an,并且是不减的,满足ai <= ai+1(1<=i<n)。现有m次询问,每个询问的结果为[l,r]区间内出现次数最多的数出现了多少次。

思路无脑线段树维护区间最多和左右值及左右值出现次数 (既然是rmq专题,当然是rmq了) 由于数列有序是不减的所以相同的值在一起,用数组b[i]记录当前值a[i]是第几个出现的相同值,然后就可以rmq查询最大出现次数了,但是这样有一个问题就是如果查到的值是问询区间左端,前面有一部分不再区间里,所以再开一个c[]数组,倒着扫一下,记录当前与每个a[i]相同值的连续序列的右边界是什么,在查询的时候直接分左端l到c[l],再从c[l]+1到r进行rmq最后二者取max即可

//POJ3368
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
const int maxn=1e5+5;

int f[maxn][18],a[maxn],b[maxn],c[maxn],n,q;

void rmqinit(){
	for(int i=1;i<=n;i++) f[i][0]=b[i];
	for(int j=1;j<=17;j++)
		for(int i=1;i+(1<<j)-1<=n;i++)
			f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}

int rmq(int l,int r){
	if(l>r) return 0;
	int d=log2(r-l+1);
	return max(f[l][d],f[r-(1<<d)+1][d]);
}

int main()
{
	while(~scanf("%d",&n)&&n){
		scanf("%d",&q);
		for(int i=1;i<=n;i++) scanf("%d",&a[i]);
		for(int i=1;i<=n;i++){
			if(i==1||a[i]!=a[i-1]) b[i]=1;
			else b[i]=b[i-1]+1;
		}
		int rbound=n;
		for(int i=n;i>0;i--){
			if(i==n||b[i]==b[i+1]-1) c[i]=rbound; 
			else c[i]=rbound=i;
		}
		rmqinit();
		while(q--){
			int l,r;
			scanf("%d%d",&l,&r);
			int ans1=min(c[l],r)-l+1;
			int ans2=rmq(min(c[l],r)+1,r);
			printf("%d\n",max(ans1,ans2));
		}
	}
}

HDU3183

题意:给一串数长度为n,让你从中删m个数,使得剩下的数刨去前导零之后最小

思路:这一上来当然想到的是无脑贪心啊,考虑只删一个数,最优是从前往后扫,当遇到一个相邻递减的数对,就把前面那个大的删掉,这个题的数据范围暴力也是可以过的,但是!rmq怎么做呢???看了别人的思路之后才懂,答案是n-m位数(保留前导零的话),先rmq找1到(m+1)里面最小的数(第id个),这个数肯定是答案的第一位,然后再找id+1到m+2最小的,这个是答案的第二位。。。。以此类推。
对了rmq维护的是区间最小值的下标,不是值,不想用三目运算符,所以就重载了min函数。。。
坑点:如果都删的话最后要输出0,不是空,wa了好久。。。

//HDU3183
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
const int maxn=1e3+5;

char ch[maxn];
int m,n,f[maxn][11],ans[maxn];

inline int min(int x,int y){
	if(ch[x]==ch[y]) return x<y?x:y;
	return ch[x]<ch[y]?x:y;
}

void rmqinit(){
	for(int i=1;i<=n;i++) f[i][0]=i;
	for(int j=1;j<=10;j++)
		for(int i=1;i+(1<<j)-1<=n;i++)
			f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}

int rmq(int l,int r){
	int d=log2(r-l+1);
	return min(f[l][d],f[r-(1<<d)+1][d]);
}

int main()
{
	while(~scanf("%s%d",ch+1,&m)){
		n=strlen(ch+1);
		rmqinit();
		int l=0,r=m+1,cnt=0;
		while(r<=n){
			l=rmq(l+1,r);
			r++;
			ans[++cnt]=l;
		}
		if(m==n){puts("0");continue;}
		int st=1;
		while(ch[ans[st]]=='0'&&st<cnt) st++; 
		for(int i=st;i<=cnt;i++) printf("%c",ch[ans[i]]);
		puts("");
	}
}

HDU3486

题意:给n个数,求最小的段数,使得每一段的最大值之和大于给定的k。每一段的长度相等,最后若干个丢掉。
思路:这题要求最少的区间数使得所有区间的最大值之和大于k,先预处理每个区间的最大值,然后枚举区间个数(二分理论上不对,但是能AC,emmm),计算和是否大于k,最小的区间数就是答案

//HDU3486
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn=2e5+5;

int n,k,a[maxn],f[maxn][19];

void rmqinit(){
	for(int i=1;i<=n;i++) f[i][0]=a[i];
	for(int j=1;j<=18;j++)
		for(int i=1;i+(1<<j)-1<=n;i++)
			f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}

int rmq(int l,int r){
	int d=log2(r-l+1);
	return max(f[l][d],f[r-(1<<d)+1][d]);
}

int main()
{
	while(~scanf("%d%d",&n,&k)){
		if(n<0&&k<0) break;
		int sum=0,maxx=0;
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			sum+=a[i],maxx=max(maxx,a[i]);
		}
		if(sum<=k){
			puts("-1");
			continue;
		}
		rmqinit();
		int i;
		for(i=max(1,(k+1)/maxx);i<=n;i++){
			int len=n/i;
			int tot=0;
			for(int j=1;j<=i;j++){
				tot+=rmq((j-1)*len+1,j*len);
				if(tot>k) break;
			}
			if(tot>k) break;
		}
		printf("%d\n",i);
	}
}

二维rmq(2道):

其实二维rmq和一维rmq的思想差不多,用于解决二维区间最值问题,有两种不同的实现方法

法一:1) O(nmlog(m))预处理----O(n)查询,把每一行都当成一维RMQ处理
法二:2) O(nmlog(n)*log(m)预处理----O(1)查询,f[ i ][ j ][ k ][ l ]表示从第一个点也就是左上角(i,j)起,到右下角(i+2^k, j+2^l)但是不包括右下角和右列和下行 ,然后预处理和查询的时候都把大矩形分成四个小矩形。

【注意】预处理的时候除了和一维rmq一样处理f[i][j][0][0],同时千万不要忘了和一维一样的方法处理f[i][j][0][x]和f[i][j][x][0]的情形。

POJ2019

题意:没什么好说的了,就是板子题,只不过问询区间是正方形边长是固定的b,给左上角坐标查询最大与最小的差

//POJ2019
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn=255;

int n,b,k;
int mx[maxn][maxn][9][9],mi[maxn][maxn][9][9];
int a[maxn][maxn];

void rmqinit(){
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++) 
			mx[i][j][0][0]=mi[i][j][0][0]=a[i][j];
	for(int j=1;j<=n;j++)
		for(int k1=1;k1<=8;k1++)
			for(int i=1;i+(1<<k1)-1<=n;i++)
				mx[i][j][k1][0]=max(mx[i][j][k1-1][0],mx[i+(1<<(k1-1))][j][k1-1][0]),
				mi[i][j][k1][0]=min(mi[i][j][k1-1][0],mi[i+(1<<(k1-1))][j][k1-1][0]);
	for(int i=1;i<=n;i++)
		for(int k2=1;k2<=8;k2++)
			for(int j=1;j+(1<<k2)-1<=n;j++)
				mx[i][j][0][k2]=max(mx[i][j][0][k2-1],mx[i][j+(1<<(k2-1))][0][k2-1]),
				mi[i][j][0][k2]=min(mi[i][j][0][k2-1],mi[i][j+(1<<(k2-1))][0][k2-1]);
	for(int k1=1;k1<=8;k1++)
		for(int k2=1;k2<=8;k2++)	
			for(int i=1;i+(1<<k1)-1<=n;i++)
				for(int j=1;j+(1<<k2)-1<=n;j++)
					mx[i][j][k1][k2]=max(max(mx[i][j][k1-1][k2-1],mx[i+(1<<(k1-1))][j][k1-1][k2-1]),max(mx[i][j+(1<<(k2-1))][k1-1][k2-1],mx[i+(1<<(k1-1))][j+(1<<(k2-1))][k1-1][k2-1])),
					mi[i][j][k1][k2]=min(min(mi[i][j][k1-1][k2-1],mi[i+(1<<(k1-1))][j][k1-1][k2-1]),min(mi[i][j+(1<<(k2-1))][k1-1][k2-1],mi[i+(1<<(k1-1))][j+(1<<(k2-1))][k1-1][k2-1]));
}

int rmq(int x1,int y1,int x2,int y2){
	int d1=log2(x2-x1+1);
	int d2=log2(y2-y1+1);
	int maxx=max(max(mx[x1][y1][d1][d2],mx[x2-(1<<d1)+1][y1][d1][d2]),max(mx[x1][y2-(1<<d2)+1][d1][d2],mx[x2-(1<<d1)+1][y2-(1<<d2)+1][d1][d2]));
	int minn=min(min(mi[x1][y1][d1][d2],mi[x2-(1<<d1)+1][y1][d1][d2]),min(mi[x1][y2-(1<<d2)+1][d1][d2],mi[x2-(1<<d1)+1][y2-(1<<d2)+1][d1][d2]));
	return maxx-minn;
}

int main()
{
	scanf("%d%d%d",&n,&b,&k);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			scanf("%d",&a[i][j]);
	rmqinit();
	while(k--){
		int r,c;
		scanf("%d%d",&r,&c);
		printf("%d\n",rmq(r,c,r+b-1,c+b-1));
	}
}

HDU2888

题意:给定一个n * m的矩阵,再给定q个询问,每次询问(r1,c1)为左上角,(r2,c2)为右下角的子矩形的最大值,并且判断该最大值是否出现在了这个子矩阵的4个顶角上
思路:也是板子题。。。

//HDU2888
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn=305;

int m,n,q;
int mx[maxn][maxn][9][9];

void rmqinit(){
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++) 
			scanf("%d",&mx[i][j][0][0]);
	for(int j=1;j<=m;j++)
		for(int k1=1;k1<=8;k1++)
			for(int i=1;i+(1<<k1)-1<=n;i++)
				mx[i][j][k1][0]=max(mx[i][j][k1-1][0],mx[i+(1<<(k1-1))][j][k1-1][0]);
	for(int i=1;i<=n;i++)
		for(int k2=1;k2<=8;k2++)
			for(int j=1;j+(1<<k2)-1<=m;j++)
				mx[i][j][0][k2]=max(mx[i][j][0][k2-1],mx[i][j+(1<<(k2-1))][0][k2-1]);
	for(int k1=1;k1<=8;k1++)
		for(int k2=1;k2<=8;k2++)	
			for(int i=1;i+(1<<k1)-1<=n;i++)
				for(int j=1;j+(1<<k2)-1<=m;j++)
					mx[i][j][k1][k2]=max(max(mx[i][j][k1-1][k2-1],mx[i+(1<<(k1-1))][j][k1-1][k2-1]),max(mx[i][j+(1<<(k2-1))][k1-1][k2-1],mx[i+(1<<(k1-1))][j+(1<<(k2-1))][k1-1][k2-1]));
}

int rmq(int x1,int y1,int x2,int y2){
	int d1=log2(x2-x1+1);
	int d2=log2(y2-y1+1);
	return max(max(mx[x1][y1][d1][d2],mx[x2-(1<<d1)+1][y1][d1][d2]),max(mx[x1][y2-(1<<d2)+1][d1][d2],mx[x2-(1<<d1)+1][y2-(1<<d2)+1][d1][d2]));
}

int main()
{
	while(~scanf("%d%d",&n,&m)){
		rmqinit();
		for(scanf("%d",&q);q;q--){
			int r1,c1,r2,c2;
			scanf("%d%d%d%d",&r1,&c1,&r2,&c2);
			int maxx=rmq(r1,c1,r2,c2);
			printf("%d ",maxx);
			if(mx[r1][c1][0][0]==maxx||mx[r1][c2][0][0]==maxx||mx[r2][c1][0][0]==maxx||mx[r2][c2][0][0]==maxx) puts("yes");
			else puts("no");
		}		
	}
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值