区间信息维护与查询(ST表)

目录

原理1:倍增

原理2:ST

原理3:RMQ

题目训练:

1.区间最差值

2.最频繁数

3.最小分段数

4.二维区间最值差


原理1:倍增

任意整数都可以表示为若干个2的幂项之和,也就是可以转化成二进制数字。所以可以通过倍增的思想,只考虑2的整数次幂的位置,快速缩小数据范围。

原理2:ST(稀疏表)

需要理解的是数组的含义,F[i][j]的含义为从i开始,长度为2^j,此区间内最大值。

需要注意的是两个for循环里面的i和j,以及k的含义。


 void ST_create()//创建ST表
 {
 	
 	for(int i = 1;i<=N;i++)//初始化 相当于Fmax[1][1] = num[1]
 	{
 		Fmax[i][0] = num[i];
	 }
 	int k = log2(N);
 	for (int j = 1;j<=k;j++)//逐步将St表打出来
 	{
 		for (int i = 1;i<=N-(1<<j)+1;i++)
 		{
 			Fmax[i][j] = max(Fmax[i][j-1],Fmax[i+(1<<(j-1))][j-1]);
		 }
	 }
 }

原理3:RMQ(区间最值查询)

int ST_query(int l,int r)
 {
 	int k = log2(r-l+1);
 	return max(F[l][k],F[r-(1<<k)+1][k]);
 }

因为任意两个区间不可能正巧是2的整数幂次,所以可以通过将一个大区间平分成两个小区间,再取最大值,这样虽然中间也有重叠,但是查询两次仍是O1。

总体分析,时间复杂度为O(nlogn),可以解决1e6。

题目训练:

1.区间最差值

AcWing1274  奶牛排队

 题目大意为给出一个容量为N的数组,查询Q次,每次打印出给定区间的最大值与最小值之间的差值。根据ST表中,查询区间最大值的方法,加入最小值。从而可以得出答案。是一个标准的板子题。

 /*
 ST表采用倍增思想 创建表耗费n*log(n) 查询O(1)
 */
 
 #include <iostream>
 #include <cmath>
 using namespace std;
 #define maxn 100005
 int Fmax[maxn][30];//Fmax[i][j]表示的含义为[i,i+2^j-1]区间内的最大值 区间长度为26j
 int Fmin[maxn][30];//同上 为区间内最小值
 int N;
 int num[maxn];
 
 void ST_create()//创建ST表
 {
 	
 	for(int i = 1;i<=N;i++)//初始化 相当于Fmax[1][1] = num[1]
 	{
 		Fmax[i][0] = num[i];
 		Fmin[i][0] = num[i];
	 }
     
     int k = log2(N);
 	for (int j = 1;j<=k;j++)//将St表打出来
 	{
 		for (int i = 1;i<=N-(1<<j)+1;i++)
 		{
 			Fmax[i][j] = max(Fmax[i][j-1],Fmax[i+(1<<(j-1))][j-1]);
 			Fmin[i][j] = min(Fmin[i][j-1],Fmin[i+(1<<(j-1))][j-1]);
		 }
	 }
 		
 }
 
 int RMQ(int l,int r)//查询区间最值  这里根据题目要求返回最值差
 {
 	int k = log2(r-l+1);
 	int m1 = max(Fmax[l][k],Fmax[r-(1<<k)+1][k]);//这里要保证区间完全覆盖 相当于最左边是l 最右边是r
 	int m2 = min(Fmin[l][k],Fmin[r-(1<<k)+1][k]);
//  	cout << m1 <<" " << m2 <<"\n";
 	return m1-m2;//q区间最值差
 }
 
 int main()
 {
// 	cin >> N >> Q;
	int Q;
	scanf ("%d %d",&N,&Q);
 	for (int i = 1;i<=N;i++)
 	{
 		scanf ("%d",&num[i]);
//		cin >> num[i];
	 }
	 ST_create();
     
	 for (int i = 1;i<=Q;i++)
	 {
	 	int l,r;
	 	cin >> l >> r;
	 	cout << RMQ(l,r);
	 	if (i!=Q) cout << "\n";
	 }
 	return 0;
 }

第一次写的时候,一直没有过去,出现的问题在于k的取值,因为有的编译器没有log2,而且不支持万能头,导致k的取值出现错误。其他地方的思路和ST表的思想相同。

2.最频繁数

POJ3368 Frequent values

 

题目大意为,有n个整数,非递减序列,对于每一个索引i,j确定在区间i到j当中出现最多的那个数的次数。有多组测试用例。

仔细思索过后,可以通过输入n个数,统计数字出现的次数,存到F【i】【0】中,然后就相当于维护i到j区间的最大值即可。需要注意的一点是,每次查询的左端,有可能出现截断,这样会导致经典当中的ST查询不准确,所以对于l端的数字,要进行特别统计,然后再和剩余部分进行比较。这相当于和上面ST表一个不同点吧。其余部分,大同小异。

//#include <bits/stdc++.h>
#include<iostream>
using namespace std;
#define maxn 1000000

/*
10 3
-1 -1 1 1 1 1 3 10 10 10
2 3
1 10
5 10
0
	*/    

int lb[maxn];//存放所有的log值 以便使用

void Initlog()
{
    lb[0] = -1;
    for (int i = 1;i<maxn;i++)
    {
        lb[i] = (i&(i-1))?lb[i-1]:lb[i-1]+1;
    }
}

int F[maxn][20];

void ST(int n)//每次测试样例的n值都不同 所以将n作为参数
{
    for(int j = 1;j<=lb[n];j++)
    {
        for (int i = 1;i<=n-(1<<j)+1;i++)
        {
            F[i][j] = max(F[i][j-1],F[i+(1<<(j-1))][j-1]);
        }
    }
}

void showST(int n)
{
	for(int j = 0;j<=lb[n];j++)
    {
        for (int i = 1;i<=n-(1<<j)+1;i++)
        {
            cout << F[i][j]<<" ";
        }
        cout << "\n";
    }
}

int RMQ(int l,int r)//求l到r区间的最大值 这是典型的 后面还需要处理
{
    if (l>r) return 0;
    int k = lb[r-l+1];//这里的加1必须在中括号里面加 里面的左右能写错????
    return max(F[l][k],F[r-(1<<k)+1][k]);
}



int a[maxn];

int main()
{
	ios::sync_with_stdio(false);//加速
	cin.tie(0);
    int n,q;
    Initlog();
    
    while (cin>>n)
    {
        if (n==0) return 0;
        cin >> q;
        F[1][0] = 1;
        for (int i = 1;i<=n;i++)//初始化了最大数 下面只需要维护区间即可 还要注意首尾的信息
        {
            cin >> a[i];
            if (i>=2)
            {
                if (a[i]==a[i-1]) F[i][0] = F[i-1][0]+1;
                else F[i][0] = 1;
            }
        }
        
        ST(n);
        
    	while(q--)
    	{
    		int l,r;
    		cin >> l >> r;
    		int ans = 0;
    		for (int i = l;i<=r;i++)
    		{
    			if (a[i]==a[l]) ans++;
    			else break;
			}
			cout << max(ans,RMQ(l+ans,r)) <<"\n";//这里面要把和第一个相同的的剔除 防止影响结果;
		}
        
    }
    return 0;
}

这段代码写的时候还出现过几个问题,找的最多的就是区间查询,那个k值,前几次将l与r的位置颠倒,并且+1位置写在了外面。还有就是超时问题,我优化了输入输出,其次将统计最左边那个数出现次数做了一个break。因为有多组数据,所以提前将log2值进行了存储。

 

3.最小分段数

 题目意为,将n个数字分成m段,每一段里面挑选一个最大值,保证所有最大值的和要大于k的前提下,输出最小的m符合值。有多组测试数据。 

 

4.二维区间最值差

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值