【poj2019 Cornfields】RMQ入门

http://poj.org/problem?id=2019 你懂的

题意:给出一个矩阵,输出询问的子矩阵中最大值与最小值的差。


当然是直接RMQ啦!但是对于萌新来说,二维RMQ还是有点懵懵的,QAQ。。。
既然二维的RMQ有点懵懵的,那就先从一维RMQ开始吧!

一维RMQ

基于倍增的思想,我们就可以很暴力地搞出一维RMQ的模板!
(照着kuangbindalao的打的,还没有验证。。但应该不会错吧。。。)

//一维
const int N = 50010;
int dp[N][20];
int mm[N];//是一个辅助数组,帮助查询区间长度是2的多少次方!
//初始化,数组下标从1开始,求最大值
void initRMQ(int n,int b[]){
	mm[0] = -1;
	for(int i = 1; i <= n; ++ i){
		mm[i] = ((i & (i-1) == 0)? mm[i-1]+1:mm[i-1]);
		dp[i][0] = b[i];
	}
	for(int j = 1; j <= mm[n]; ++ 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 rmq(int x,int y){
	int k = mm[y-x+1];
	return max(dp[x][k], dp[y-(1<<k)+1][k]);
}

其中至关重要的两句话,就是初始化和查询的时候,带max函数的那两句话啦。
d p [ i ] [ j ] = m a x ( d p [ i ] [ j − 1 ] , d p [ i + ( 1 &lt; &lt; ( j − 1 ) ) ] [ j − 1 ] ) dp[i][j] = max(dp[i][j-1],dp[i+(1&lt;&lt;(j-1))][j-1]) dp[i][j]=max(dp[i][j1],dp[i+(1<<(j1))][j1])
d p [ i ] [ j ] dp[i][j] dp[i][j]表示以 i i i为起点,长度为 2 j − 1 2^{j}-1 2j1的区间内的最大值。
所以状态转移就是
当 前 要 求 的 区 间 = 之 前 求 过 的 两 段 刚 好 可 以 拼 成 当 前 区 间 的 区 间 取 最 大 值 当前要求的区间=之前求过的两段刚好可以拼成当前区间的区间取最大值 =
[图片]
啊。。图好丑。。反正就是这么个意思。。。
嗯。。那查询的时候,大概就是这么个意思:
黑色的是查询区间。
在这里插入图片描述
呼,一维的大概就是这样啦!


二维RMQ

初始化的时候,我们是这样初始化的(我真的不是故意这么缩进的!但是不这样的话阅读体验着实有些差。。)

	for(int i = 1; i <= n; ++ i)
	for(int j = 1; j <= m; ++ j)
		dp[i][j][0][0] = val[i][j];
			
	for(int ii = 0; ii <= mm[n]; ++ ii)
	for(int jj = 0;jj <= mm[m]; ++ jj)
		if(ii + jj)
			for(int i = 1; i+(1<<ii)-1 <= n; ++ i)
			for(int j = 1; j+(1<<jj)-1 <= m; ++ j)
				if(ii)
					dp[i][j][ii][jj] = max(dp[i][j][ii-1][jj],
										   dp[i+(1<<(ii-1))][j][ii-1][jj]);
				else
					dp[i][j][ii][jj] = max(dp[i][j][ii][jj-1], 
										   dp[i][j+(1<<(jj-1))][ii][jj-1]); 

其实和一维差不多啦!把线看成面就好了~(或者把线画得很粗很粗的)
但是这样的话还是有个小问题(所以需要特判 i i 和 j j 嘛~)
比如我们是横向拓展的,那纵向(也就是很粗很粗的线的直径)该怎么拓展呢。
当然是特判一下,和横向一样的拓展了 = = ~


言归正传回归本题(没什么可说的了)。。。。。。

AC代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
/*********************
//一维
const int N = 50010;
int dp[N][20];
int mm[N];
//初始化,数组下标从1开始,求最大值
void initRMQ(int n,int b[]){
	mm[0] = -1;
	for(int i = 1; i <= n; ++ i){
		mm[i] = ((i & (i-1) == 0)? mm[i-1]+1:mm[i-1]);
		dp[i][0] = b[i];
	}
	for(int j = 1; j <= mm[n]; ++ 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 rmq(int x,int y){
	int k = mm[y-x+1];
	return max(dp[x][k], dp[y-(1<<k)+1][k]);
}
*********************/
/******************************************/
//二维
//数组下标1开始,预处理复杂度n*m*logn*logm
int val[310][310];
int dp[310][310][9][9];//最大值
int mm[310];//二进制数-1,使用前初始化
void initRMQ(int n,int m){
	for(int i = 1; i <= n; ++ i)
		for(int j = 1; j <= m; ++ j)
			dp[i][j][0][0] = val[i][j];
	for(int ii = 0; ii <= mm[n]; ++ ii)
		for(int jj = 0;jj <= mm[m]; ++ jj)
			if(ii + jj)
				for(int i = 1; i+(1<<ii)-1 <= n; ++ i)
					for(int j = 1; j+(1<<jj)-1 <= m; ++ j)
						if(ii) dp[i][j][ii][jj] = max(dp[i][j][ii-1][jj], dp[i+(1<<(ii-1))][j][ii-1][jj]);
						else dp[i][j][ii][jj] = max(dp[i][j][ii][jj-1], dp[i][j+(1<<(jj-1))][ii][jj-1]); 
}
//查询矩形内最大值(x1<=x2,y1<=y2)
int rmq(int x1,int y1,int x2,int y2){
	int k1 = mm[x2-x1+1];
	int k2 = mm[y2-y1+1];
	x2 -= (1<<k1)-1;
	y2 -= (1<<k2)-1;
	return max(max(dp[x1][y1][k1][k2],dp[x1][y2][k1][k2]),max(dp[x2][y1][k1][k2], dp[x2][y2][k1][k2]));
}
int main(){
	//在外边对mm初始化
	mm[0] = -1;
	for(int i = 1; i <= 305; ++ i)
		mm[i] = (i&(i-1)) == 0 ? mm[i-1]+1:mm[i-1];
	int n,m,Q;
	int r1,r2,c1,c2;
	while(scanf("%d%d",&n,&m) == 2){
		for(int i = 1; i <= n; ++ i)
			for(int j = 1; j <= m; ++ j)
				scanf("%d",&val[i][j]);
		initRMQ(n,m);
		scanf("%d",&Q);
		while(Q --){
			scanf("%d%d%d%d",&r1,&c1,&r2,&c2);
			if(r1 > r2) swap(r1,r2);
			if(c1 > c2) swap(c1,c2);
			int tmp = rmq(r1,c1,r2,c2);
			printf("%d\n",tmp);
		}
	}
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值