题目描述
方法一:暴力枚举(过80%数据)
思路
枚举每一个序号i所对应的范围[i - k ,i + k],不断更新最小值
代码
#include <iostream>
using namespace std;
const int N = 1000010;
int a[N];
int n, k;
int main()
{
cin >> n;
for(int i = 1; i <= n; i ++) cin >> a[i];
cin >> k;
for(int i = 1; i <= n; i ++)
{
int temp = N;
for(int j = -k; j <= k; j ++)
{
if(i + j < 1 || i + j > n) continue;
temp = min(temp, a[i + j]);
}
if(temp != N) cout << temp << " ";
}
return 0;
}
方法二:单调队列
思路
基于方法一的思路与样例的结果,可以发现,不同序号对应的最小值是相同的,说明在一定范围内,最小值是不变的,因此可以用单调队列维护这样的结果
保证单调队列的首元素为最小值的下标,当一个元素>队列对应尾元素时,将其下标直接队列;否则不断删除队列尾元素,直至当前元素>尾元素
单调队列里存放的是下标!!!
前提知识:数组模拟队列
//q存放队列元素,hh存放队首下标,tt存放队尾下标
int q[N],hh,tt=-1;
//队尾插入
q[++tt]=x;
//弹出
hh++;
//判断队列是否为空
if(hh<=tt) not empty
else empty
//取出队首或队尾元素
q[tt],q[hh];
AC代码
#include <iostream>
using namespace std;
const int N = 1000010;
int a[N];
int q[N]; //采用数组模拟双端队列,速度更快
int n, k;
int main()
{
cin >> n;
for(int i = 1; i <= n; i ++) cin >> a[i];
cin >> k;
int hh = 0, tt = -1; //指向双端队列的头、尾指针
int r = 1;//右边界, 便于后期判断当前元素是否应该加入队列
for(int i = 1; i <= n; i ++) //遍历所有序号
{
int left = i - k, right = i + k; //序号i最小值的下标范围[i - k, i + k]
//将范围控制在(1, n)内,其他下标无效
left = max(left, 1);
right = min(right, n);
while(hh <= tt && q[hh] < left) hh ++; //首元素对应的下标不在[left, right]内
while(r <= right) //这里可以解释为什么后期我们可以直接输出结果,因为我们限制了右边界,这样我们所寻找的最小值的下标一定在[i - k, i + k]
{
while(hh <= tt && a[q[tt]] > a[r]) tt --; //保证加入新元素后,单调队列仍为严格递增状态
q[++ tt] = r; //将新元素的下标加入队列
r ++;
}
cout << a[q[hh]] << " "; //由于我们之前已经将范围确定在合理范围内,因此这里不需要在进行判断,直接输出即可
}
return 0;
}
相关题目
方法三:ST表
ST表用于解决RMQ问题,初始化时间复杂度为O(nlogn),查询时间为O(1)
前导知识:ST表详解
AC代码
#include <iostream>
using namespace std;
const int N = 1000010, M = 21;
int a[N];
int Log2[N]; //log2[i]表示以2为底i的对数值
int f[N][M]; //f[i][j]表示起始点下标为i,长度为2^j区间内的最大值
int n;
void Log2_Init() //不使用math.h库函数中的log2,手动实现
{
Log2[1] = 0;
Log2[2] = 1;
for(int i = 3; i <= N; i ++) Log2[i] = Log2[i / 2] + 1;
}
void ST_Init() //ST表初始化操作
{
for(int i = 1; i <= n; i ++) f[i][0] = a[i];
int k = Log2[n];
for(int j = 1; j <= k; j ++)
{
for(int i = 1; i <= n - (1 << j) + 1; i ++)
f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}
}
int ST_Query(int l, int r) //ST表查询操作
{
int k = Log2[r - l + 1];
return min(f[l][k], f[r - (1 << k) + 1][k]);
}
int main()
{
Log2_Init();
cin >> n;
for(int i = 1; i <= n; i ++) cin >> a[i];
ST_Init();
int k;
cin >> k;
for(int i = 1; i <= n; i ++)
{
int l = max(i - k, 1);
int r = min(i + k, n);
cout << ST_Query(l, r) << " ";
}
return 0;
}
#include <iostream>
#include <cmath> //使用log2函数
using namespace std;
const int N = 1000010, M = 21; //由于我们还要扩大n+1~n+k的空间,因此将数组范围开大一倍
int a[N];
int f[N][M]; //f[i][j]表示起始点下标为i,长度为2^j区间内的最大值
int n;
void ST_Init()
{
for(int i = 1; i <= n; i ++) f[i][0] = a[i];
int k = log2(n);
for(int j = 1; j <= k; j ++)
{
for(int i = 1; i <= n - (1 << j) + 1; i ++)
f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}
}
int ST_Query(int l, int r)
{
int k = log2(r - l + 1);
return min(f[l][k], f[r - (1 << k) + 1][k]);
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i ++) cin >> a[i];
ST_Init();
int k;
cin >> k;
for(int i = 1; i <= n; i ++)
{
int l = max(i - k, 1);
int r = min(i + k, n);
cout << ST_Query(l, r) << " ";
}
return 0;
}
相关题目
ST表模板题
欢迎大家批评指正!!!