更新中……
单调队列
问题背景
给定一个数组,依次求所有长度为 m m m 的区间的元素最大值与最小值。
简介
构建一个空队列。
以求最小值为例,从左往右扫描一遍数组。 如果队首元素所在位置超过了区间范围,则将其弹出。每当扫描到一个元素时,将队列末尾大于其的一部分舍弃,并将其放置在队列尾。
由此得到的队列是单调递增的,并且队首元素是当前区间的最小值。
例题
代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
#include<iostream>
#include<algorithm>
#include<queue>
#define con(_c,_t,_f) ((_c)?(_t):(_f))
#define ll long long int
#define MAX 9000000
using namespace std;
ll q[MAX*3];
int head=0, tail=0; // [head,tail)
bool empty(){ return head==tail; }
ll front(){ return q[head]; }
ll back(){ return q[tail-1]; }
void push(ll x){ q[tail++]=x; }
ll a[MAX];
int n,k;
int main(){
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++) scanf("%lld",a+i);
for(int i=0;i<n;i++){
if(i>=k&&q[head]==i-k) head++;
while(!empty()&&a[back()]>a[i])tail--;
push(i);
if(i>=k-1) printf("%lld ",a[q[head]]);
}
printf("\n");
head=tail;
for(int i=0;i<n;i++){
if(i>=k&&q[head]==i-k) head++;
while(!empty()&&a[back()]<a[i])tail--;
push(i);
if(i>=k-1) printf("%lld ",a[q[head]]);
}
}
线段树
问题背景
一个数组,每次操作是一次维护或一次询问:
- 选定一个区间,区间内所有数加相同的值;
- 选定一个区间,求区间内所有数的和;
当数组很大时,这样的操作时间复杂度最坏可以达到 O ( Q ⋅ N ) O(Q\cdot N) O(Q⋅N),其中 Q Q Q 是操作次数, N N N 是数组长度。
线段树可以将操作的时间复杂度降低至 O ( Q ⋅ l o g N ) O(Q\cdot logN) O(Q⋅logN)。
简介
线段树思想来源于二分,将整个线段二分,二分成的子线段继续二分,如此往复,直到单位线段。
查找一个单元时,它必定仅属于两条子线段之一,递归访问即可查找,修改一个单元同理。
问题是查找和修改一个区间。其实也不难,只需要在每个节点处添加一个标记,每次向下访问时传递这个标记。如果某一次需要的操作恰好是对这个完整区间进行操作,则可以在此处添加一个该操作标记,不必再向下访问,直接折返向上维护至根节点即可。