欢迎来到清街老酒的博客

どんな別れがあったとしても、出会ったことには必ず意味がある

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;
}
posted @ 2020-08-19 16:14  清街老酒  阅读(123)  评论(0编辑  收藏  举报