目录
原理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.区间最差值
题目大意为给出一个容量为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.最频繁数
题目大意为,有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.二维区间最值差