「数据结构」第3章 RMQ问题课堂过关

「数据结构」第3章 RMQ问题课堂过关

A. 【例题1】数列区间

题目

code

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>

#define nn 100010
using namespace std;
int read() {
	int re = 0;
	char c = getchar();
	while(c < '0' || c > '9')c = getchar();
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0',
		c = getchar();
	return re;
}

int n , m;
int st[nn * 2][25];
inline int max_(int a , int b) {return a > b ? a : b;}
int main() {
	memset(st , 0x81 , sizeof(st));
	
	n = read();	m = read();
	for(int i = 1 ; i <= n ; i++)
		st[i][0] = read();
	
	int k = log(n) / log(2) + 1;
	for(int j = 1 ; j <= k ; j++) {
		for(int i = 1 ; i <= n ; i++) {
			st[i][j] = max_(st[i][j - 1] , st[i + (1 << j - 1)][j - 1]);
		}
	}
	while(m--) {
		int l = read() , r = read();
		k = log(r - l + 1) / log(2);
		printf("%d\n" , max_(st[l][k] , st[r - (1 << k) + 1][k]));
	}
	return 0;
}

B. 【例题2】静态区间

题目

code

#include <iostream>
#include <cstdio>
#include <cmath>
#define N 50010
using namespace std;
int read() {
	int re = 0;
	char c = getchar();
	bool sig = false;
	while(c < '0' || c > '9') {
		if(c == '-')	sig = true;
		c = getchar();
	}
	while(c >= '0' && c <= '9')
		re  = (re << 1) + (re << 3) + c - '0' , c = getchar();
	return sig ? -re : re;
}
int n , m;
int st[N * 2][25];
int gcd(int a , int b) {
	if(a == 0) return b;
	return b == 0 ? a : gcd(b , a % b);
}
int main() {
	n = read() , m = read();
	for(int i = 1 ; i <= n ; i++)
		st[i][0] = read();
	
	for(int j = 1 ; (1 << j) <= n ; j++)
		for(int i = 1 ; i <= n ; i++)
			st[i][j] = gcd(st[i][j - 1] , st[i + (1 << j - 1)][j - 1]);
			
	for(int i = 1 ; i <= m ; i++) {
		int l = read() , r = read();
		int k = log(r - l + 1) / log(2);
		printf("%d\n" , gcd(st[l][k] , st[r - (1 << k) + 1][k]));
	}
	return 0;
}

C. 【例题3】与众不同

题目

思路

说明:这题我的方法是线段树而非RMQ

做法比较奇妙,结合代码讲下:

//读入
	n = read() , m = read();
	segt.root = segt.build(1 , n);
	for(int i = 1 ; i <= n ; i++)
		a[i] = read();//原数据存储的数组
	for(int i = 1 ; i <= m ; i++)//询问
		q[i].l = read() + 1 , q[i].r = read() + 1 , q[i].id = i;
	
	sort(q + 1 , q + m + 1 , cmp);//按照询问的右端点排序
	Discretize(a + 1 , a + n + 1);//离散化

不难想到,从\(i\)位置开始,最长的完美序列长度是可以\(O(n)\)求出来的:

	for(int i = 1 , p = 0 ; i <= n ; i++) {
		while(p < n && vis[a[p + 1]] == false)
			vis[a[p + 1]] = true , ++p;
		r[i] = p;//[i,r[i]]这一段为完美序列,且r[i]最大
		vis[a[i]] = false;
	}

处理每一个询问并输出,先贴出代码(主程序就这么短啦):

	for(int i = 1 , p = 0 ; i <= m ; i++) {
		while(p <= n && r[p + 1] <= q[i].r)
			++p , segt.change2(segt.root , p , r[p] - p + 1);//对p单点修改(直接修改值而不是加上一个东西)
			
		segt.change(segt.root , p + 1 , n , q[i].r - q[i - 1].r);//对[p+1,n]区间修改(直接加上一个东西)
		ans[q[i].id] = segt.ask(segt.root , q[i].l , q[i].r);//最大值查询
	}
	
	for(int i = 1 ; i <= m ; i++)
		printf("%d\n" , ans[i]);

下面做解释:

\(dat_i\)表示当前\(i\)开始的最长"完美序列"长度(\(dat_i+i-1\)不得大于当前右边界)

我们在上面把询问按照右端点从小到大排序,思考,每一次右端点扩张会对\(dat\)造成什么影响?显然,有以下情况:(下面\(i,j\)的意义与代码中不同)

\[dat_i=\begin{cases} 0 &(i >q_j.r)\\ q_j-i+1 &(q_{j-1}.r \le i \le q_j.r &\and\quad q_j.r\ge r_i)\\ dat_i+q_j.r-q_{j-1}.r &(i\le q_j.r\le r_i &\and\quad i\leq q_{j-1}.r )\\ r_i-i+1 &(r_i\le q_j.r)\\ \end{cases} \]

对于询问,直接输出区间内的最大值即可

不难看出,对于后面两种情况及查询操作,\(dat\)数组是可以用线段树维护的,为了方便第一,第二种情况,我们不妨赋初始值:\(dat_i=1-i\)(比较巧妙,自己体会一下)

把线段树,离散化,快读写上,就完成啦:

code

#include <iostream>
#include <cstdio>
#include <algorithm>
#define N 200010
#define M 200010
#define inf 0x3fffffff
using namespace std;
int read() {
	int re = 0;
	char c = getchar();
	bool sig = 0;
	while(c < '0' || c > '9') {
		if(c == '-')	sig = true;
		c = getchar();
	}
	while(c >= '0' && c <= '9')	re = (re << 1) + (re << 3) + c - '0' ,  c = getchar();
	return sig ? -re : re;
}
struct DiscretizeNode {//离散化begin
	int dat , id;
}tmp[N];
bool cmp_Dis(DiscretizeNode a , DiscretizeNode b) {
	return a.dat < b.dat;
}
void Discretize(int *a , int *ed) {
	int n = ed - a;
	for(int i = 0 ; i < n ; i++)
		tmp[i].dat = a[i] , tmp[i].id = i;
	sort(tmp , tmp + n , cmp_Dis);
	int cnt = 1;
	a[tmp[0].id] = 1;
	for(int i = 1 ; i < n ; i++)
		a[tmp[i].id] = (tmp[i].dat != tmp[i - 1].dat ? ++cnt : cnt);
}//离散化end
struct SegmentTree {//线段树begin
	struct basis {
		int l , r , ls , rs , max , tag;
	}tr[N * 4];
	#define l(_) tr[_].l
	#define r(_) tr[_].r
	#define ls(_) tr[_].ls
	#define rs(_) tr[_].rs
	#define tag(_) tr[_].tag
	inline int max_(int x , int y) {return x > y ? x : y;}
	int root;
	int build(int l , int r) {
		static int cnt = 0;
		int p = ++cnt;
		l(p) = l , r(p) = r;
		if(l == r) {
			tr[p].max = 1 - l;//这里的初始化
			return p;
		}
		int mid = (l + r) / 2;
		ls(p) = build(l , mid);
		rs(p) = build(mid + 1 , r);
		tr[p].max = max_(tr[ls(p)].max , tr[rs(p)].max);
		return p;
	}
	inline void spread(int p) {
		if(tag(p) == 0)	return;
		tr[ls(p)].max += tag(p);
		tr[rs(p)].max += tag(p);
		tag(ls(p)) += tag(p);
		tag(rs(p)) += tag(p);
		tag(p) = 0;
	}
	void change(int p , int l , int r , int dat) {//[l,r]值增加dat
		if(l > r(p) || r < l(p))	return;
		if(l <= l(p) && r >= r(p)) {
			tag(p) += dat;
			tr[p].max += dat;
			return;
		}
		spread(p);
		change(ls(p) , l , r , dat);
		change(rs(p) , l , r , dat);
		tr[p].max = max_(tr[ls(p)].max , tr[rs(p)].max);
	}
	void change2(int p , int id , int dat) {//将id位置改为dat
		if(l(p) == r(p)) {
			tr[p].max = dat;
			return;
		}
		spread(p);
		change2(l(rs(p)) <= id ? rs(p) : ls(p) , id , dat);
		tr[p].max = max_(tr[ls(p)].max , tr[rs(p)].max);
	}
	int ask(int p , int l , int r) {
		if(l > r(p) || r < l(p))	return -inf;
		if(l <= l(p) && r >= r(p))
			return tr[p].max;
		spread(p);
		return  max_(ask(ls(p) , l , r) ,  ask(rs(p) , l , r));
	}
}segt;//线段树end

struct query {
	int l , r , id;
}q[N];
bool cmp(query a , query b) {return a.r < b.r;}

int n , m ;
int a[N];
int r[N];
int ans[M];
bool vis[M];

int main() {
	n = read() , m = read();
	segt.root = segt.build(1 , n);
	for(int i = 1 ; i <= n ; i++)
		a[i] = read();
	for(int i = 1 ; i <= m ; i++)
		q[i].l = read() + 1 , q[i].r = read() + 1 , q[i].id = i;
	
	sort(q + 1 , q + m + 1 , cmp);
	Discretize(a + 1 , a + n + 1);
	
	for(int i = 1 , p = 0 ; i <= n ; i++) {
		while(p < n && vis[a[p + 1]] == false)
			vis[a[p + 1]] = true , ++p;
		r[i] = p;
		vis[a[i]] = false;
	}
	for(int i = 1 , p = 0 ; i <= m ; i++) {
		while(p <= n && r[p + 1] <= q[i].r)
			++p , segt.change2(segt.root , p , r[p] - p + 1);
			
		segt.change(segt.root , p + 1 , n , q[i].r - q[i - 1].r);
		ans[q[i].id] = segt.ask(segt.root , q[i].l , q[i].r);
	}
	
	for(int i = 1 ; i <= m ; i++)
		printf("%d\n" , ans[i]);
	return 0;
}

D. 【例题4】矩阵最值

题目

思路

二维ST表,没啥好说的,就是初始化的时候会不同,稍微理解下即可

code

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#define N 300
#define inf (int)0x80000000
using namespace std;
int read() {
	int re = 0;
	char c = getchar();
	bool sig = false;
	while(c < '0' || c > '9') {
		if(c == '-')	sig = true;
		c = getchar();
	}
	while(c >= '0' && c <= '9')
		re  = (re << 1) + (re << 3) + c - '0' , c = getchar();
	return sig ? -re : re;
}
int maxx(int a , int b , int c , int d) {
	return (a > b ? a : b) > (c > d ? c : d) ? (a > b ? a : b) : (c > d ? c : d);
}
#define max_(_ , __) ((_) > (__) ? (_) : (__))
int n , m , q;
int st[N * 2][N * 2][10][10];
int main() {
	memset(st , -0x3f , sizeof(st));
	n = read() , m = read() , q = read();
	for(int i = 1 ; i <= n ; i++)
		for(int j = 1 ; j <= m ; j++)
			st[i][j][0][0] = read();
	for(int I = 0 ; (1 << I) <= n ; I++)
		for(int J = 0 ; (1 << J) <= m ; J++) {
			if(I == 0 && J == 0)	continue;
			for(int i = 1 ; i <= n ; i++)
				for(int j = 1 ; j <= m ; j++)
					st[i][j][I][J] = (I != 0 ?
					max_(st[i][j][I - 1][J] , st[i + (1 << I - 1)][j][I - 1][J]) :
					max_(st[i][j][I][J - 1] , st[i][j + (1 << J - 1)][I][J - 1]) );
		}
	for(int i = 1 ; i <= q ; i++) {
		int lx , rx , ly , ry;
		lx = read() , ly = read() , rx = read() , ry = read();
		int kx = log(rx - lx + 1) / log(2);
		int ky = log(ry - ly + 1) / log(2);
		printf("%d\n" , maxx(
		st[lx][ly][kx][ky] ,
		st[rx - (1 << kx) + 1][ly][kx][ky] , 
		st[lx][ry - (1 << ky) + 1][kx][ky] , 
		st[rx - (1 << kx) + 1][ry - (1 << ky) + 1][kx][ky]
		));
	}
	return 0;
}
posted @ 2021-05-21 17:13  追梦人1024  阅读(73)  评论(0编辑  收藏  举报