RMQ问题
RMQ问题
RMQ (Range Minimum/Maximum Query)问题是指:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j里的最小(大)值,也就是说,RMQ问题是指求区间最值的问题。
主要方法(线段树,ST表,LCA)
1.线段树
支持修改,时间复杂度:预处理O(n),查询O(log(n))
例:HDU1754
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<string>
#include<queue>
#include<map>
#include<stack>
#include<iostream>
#define INF 0x3f3f3f3f
typedef long long ll;
using namespace std;
//定义
#define maxn 500007 //元素总个数
int Max[maxn<<2],Add[maxn<<2];//Max求最大值,Add为懒惰标记
int A[maxn],n,ANS;//存原数组数据下标[1,n]
//建树
//PushUp函数更新节点信息 ,这里是求最大值
void PushUp(int rt){Max[rt]=max(Max[rt<<1],Max[rt<<1|1]);}
//Build函数建树
void Build(int l,int r,int rt){ //l,r表示当前节点区间,rt表示当前节点编号
if(l==r) {//若到达叶节点
Max[rt]=A[l];//储存数组值
return;
}
int m=(l+r)>>1;
//左右递归
Build(l,m,rt<<1);
Build(m+1,r,rt<<1|1);
//更新信息
PushUp(rt);
}
//点修改
void Update(int L,int C,int l,int r,int rt){//l,r表示当前节点区间,rt表示当前节点编号
if(l==r){//到叶节点,修改
Max[rt]=C;
return;
}
int m=(l+r)>>1;
//根据条件判断往左子树调用还是往右
if(L <= m) Update(L,C,l,m,rt<<1);
else Update(L,C,m+1,r,rt<<1|1);
PushUp(rt);//子节点更新了,所以本节点也需要更新信息
}
//区间查询
int Query(int L,int R,int l,int r,int rt){//L,R表示操作区间,l,r表示当前节点区间,rt表示当前节点编号
if(L <= l && r <= R){
//在区间内,直接返回
return ANS=max(Max[rt],ANS);
}
int m=(l+r)>>1;
//累计答案
if(L <= m) Query(L,R,l,m,rt<<1);
if(R > m) Query(L,R,m+1,r,rt<<1|1);
return ANS;
}
int main()
{
int m,u,v;
while(cin>>n>>m)
{
memset(Max,0,sizeof(Max));
memset(Add,0,sizeof(Add));
for(int i=1;i<=n;i++)
{
scanf("%d",&A[i]);
}
Build(1,n,1);
string str;
for(int i=1;i<=m;i++)
{
cin>>str;
if(str[0]=='Q')
{
ANS=0;
scanf("%d%d",&u,&v);
printf("%d\n",Query(u,v,1,n,1));
}
else if(str[0]=='U')
{
scanf("%d%d",&u,&v);
Update(u,v,1,n,1);
}
}
}
}
2.ST表
以倍增思想为基础,不能在线修改,适合离线RMQ问题
时间复杂度: 预处理O(nlog(n)) 查询O(1)
预处理:先开辟一个数组\(f[i][j]\)表示从第i号节点到第i+2j-1号节点的最大值,即从i号节点开始往后数共2j个节点中的最大值。
查询:查询x,y之间的最大值,记len为[x,y]区间长度 ,最大值=max\((f[x][log {len}],f[y-2^{log{len}}+1][log{len}])\)
具体思路参考这篇博客
例:洛谷p3865
#pragma GCC optimize("Ofast")
#pragma GCC target("avx,avx2,fma")
#pragma GCC optimize ("unroll-loops")
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<string>
#include<queue>
#include<map>
#include<stack>
#include<iostream>
#define INF 0x3f3f3f3f
#define lowbit(a) ((a)&-(a))
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int a[110000][30],logg[110000],bin[110000];
int i,j,k,m,n,o,p,js,jl,x,y,t,lg;
int my_max(int x,int y)
{
if(x>y)return(x);
else return(y);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i][0]);
bin[0]=1;for(int i=1;i<=30;i++)bin[i]=bin[i-1]*2;
logg[0]=-1;for(int i=1;i<=n;i++)logg[i]=logg[i/2]+1;
for(int i=1;i<=logg[n];i++)
for(int j=1;j<=n;j++)
{
if(j+bin[i]-1<=n)
{
a[j][i]=my_max(a[j][i-1],a[j+bin[i-1]][i-1]);
}
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
t=y-x+1;lg=logg[t];
printf("%d\n",my_max(a[x][lg],a[y-bin[lg]+1][lg]));
}
return 0;
}
3.LCA
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
struct zzz {
int t, nex;
}e[500010 << 1]; int head[500010], tot;
void add(int x, int y) {
e[++tot].t = y;
e[tot].nex = head[x];
head[x] = tot;
}
int depth[500001], fa[500001][22], lg[500001];
void dfs(int now, int fath) { //now表示当前节点,fath表示它的父亲节点
fa[now][0] = fath; depth[now] = depth[fath] + 1;
for(int i = 1; i <= lg[depth[now]]; ++i)
fa[now][i] = fa[fa[now][i-1]][i-1]; //这个转移可以说是算法的核心之一
//意思是now的2^i祖先等于now的2^(i-1)祖先的2^(i-1)祖先
//2^i = 2^(i-1) + 2^(i-1)
for(int i = head[now]; i; i = e[i].nex)
if(e[i].t != fath) dfs(e[i].t, now);
}
int LCA(int x, int y) {
if(depth[x] < depth[y]) //用数学语言来说就是:不妨设x的深度 >= y的深度
swap(x, y);
while(depth[x] > depth[y])
x = fa[x][lg[depth[x]-depth[y]] - 1]; //先跳到同一深度
if(x == y) //如果x是y的祖先,那他们的LCA肯定就是x了
return x;
for(int k = lg[depth[x]] - 1; k >= 0; --k) //不断向上跳(lg就是之前说的常数优化)
if(fa[x][k] != fa[y][k]) //因为我们要跳到它们LCA的下面一层,所以它们肯定不相等,如果不相等就跳过去。
x = fa[x][k], y = fa[y][k];
return fa[x][0]; //返回父节点
}
int main() {
int n, m, s; scanf("%d%d%d", &n, &m, &s);
for(int i = 1; i <= n-1; ++i) {
int x, y; scanf("%d%d", &x, &y);
add(x, y); add(y, x);
}
for(int i = 1; i <= n; ++i) //预先算出log_2(i)+1的值,用的时候直接调用就可以了
lg[i] = lg[i-1] + (1 << lg[i-1] == i); //看不懂的可以手推一下
dfs(s, 0);
for(int i = 1; i <= m; ++i) {
int x, y; scanf("%d%d",&x, &y);
printf("%d\n", LCA(x, y));
}
return 0;
}