[学习笔记] rmq 问题
0. 前言
以前只会 \(\mathcal O(n\log n)-\mathcal O(1)\) 的朴素 \(\rm rmq\)… 一度以为这就是 \(\rm rmq\) 的全部…
回想起来自己真是 \(\rm naive\) 得可爱。
放一道 \(\rm rmq\) 的题:由乃救爷爷,之后的内容应该就在这题上测了…
1. 前缀后缀分块
1.1. 算法流程
将序列划分成 \(\frac{n}{B}\) 个块,对于每个 \(i\),预处理出它在所属块的前缀 \(\max\) 与后缀 \(\max\)。再对块与块之间求一个朴素 \(\rm rmq\)。
查询就是出现散块就将前缀后缀拼在一起,整块就朴素 \(\rm rmq\)。注意还有 \(l,r\) 在同一块内的情况,这个直接暴力查询。
1.2. 复杂度分析
不太好分析的是 \(l,r\) 在同一块内的情况。枚举块共 \(\frac{n}{B}\) 种情况,那么 \(l,r\) 都在此块的概率为 \(\frac{B^2}{n^2}\),所以总概率是 \(\frac{B}{n}\),单次复杂度是 \(\mathcal O(\frac{B^2}{n})\)。所以总共是 \(\mathcal O(n+\frac{n}{B}\log \frac{n}{B}+q+q\cdot \frac{B^2}{n})\)。
当 \(B\) 至少为 \(\log n\) 时,\(\frac{n}{B}\log \frac{n}{B}\) 不超过 \(n\);当 \(B\) 至多为 \(\sqrt n\) 时,\(q\cdot \frac{B^2}{n}\) 不超过 \(n\)。
所以 \(B\) 取 \(\log n\sim \sqrt n\) 时即可。还有一个 出题人心理学分析,还蛮有意思的。
可以看作是 \(\mathcal O((n+q)\cdot \sqrt{\log n})\) 的。另外,听说块长设成 \(2^k\) 会快一些。
1.3. 代码
#include <cstdio>
#define print(x,y) write(x),putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while((s=getchar())>'9' or s<'0')
f|=(s=='-');
while(s>='0' and s<='9')
x=(x<<1)+(x<<3)+(s^48),
s=getchar();
return f?-x:x;
}
template <class T>
inline void write(const T x) {
if(x<0) {
putchar('-'),write(-x);
return;
}
if(x>9) write(x/10);
putchar(x%10^48);
}
namespace GenHelper {
unsigned z1,z2,z3,z4,b;
unsigned rand_() {
b=((z1<<6)^z1)>>13;
z1=((z1&4294967294U)<<18)^b;
b=((z2<<2)^z2)>>27;
z2=((z2&4294967288U)<<2)^b;
b=((z3<<13)^z3)>>21;
z3=((z3&4294967280U)<<7)^b;
b=((z4<<3)^z4)>>12;
z4=((z4&4294967168U)<<13)^b;
return (z1^z2^z3^z4);
}
}
void srand(unsigned x) {
using namespace GenHelper;
z1=x; z2=(~x)^0x233333333U;
z3=x^0x1234598766U; z4=(~x)+51;
}
int read() {
using namespace GenHelper;
int a=rand_()&32767;
int b=rand_()&32767;
return a*32768+b;
}
#include <iostream>
using namespace std;
const int B=64,maxn=20000005;
int n,m,pre[maxn],suf[maxn];
int st[20][maxn/B+2],bl[maxn];
int lg[maxn/B+2],a[maxn];
unsigned s;
inline int Max(const int a,const int b) { return a>b?a:b; }
int calc(int l,int r) {
if(l>r) swap(l,r);
int ret=0;
if(bl[l]^bl[r]) {
ret=Max(suf[l],pre[r]);
l=bl[l]+1,r=bl[r]-1;
if(l<=r) {
int d=lg[r-l+1];
ret=Max(ret,Max(st[d][l],st[d][r-(1<<d)+1]));
}
}
else {
for(int i=l;i<=r;++i)
ret=Max(ret,a[i]);
}
return ret;
}
void init() {
for(int i=1;i<=n;++i)
if((bl[i]-1)*B+1==i)
pre[i]=a[i];
else
pre[i]=Max(pre[i-1],a[i]);
suf[n]=a[n];
for(int i=n-1;i>=1;--i)
if(bl[i]*B==i)
suf[i]=a[i];
else
suf[i]=Max(suf[i+1],a[i]);
lg[0]=-1;
for(int i=1;i<=bl[n];++i)
lg[i]=lg[i>>1]+1,
st[0][i]=pre[i*B];
for(int j=1;(1<<j)<=bl[n];++j)
for(int i=1;i+(1<<j)-1<=bl[n];++i)
st[j][i]=Max(st[j-1][i],st[j-1][i+(1<<j-1)]);
}
int main() {
n=read(9),m=read(9),s=read(9);
srand(s);
for(int i=1;i<=n;++i)
a[i]=read(),
bl[i]=(i-1)/B+1;
int l,r; init();
unsigned long long ans=0;
while(m--) {
l=read()%n+1;
r=read()%n+1;
ans+=calc(l,r);
}
print(ans,'\n');
return 0;
}
2. 压位分块
2.1. 算法流程
将 \(B\) 设为 \(\log n\),对块与块之间求一个朴素 \(\rm rmq\)。对每个块从左到右维护递减的单调栈,注意由于将状态压缩成一个数字,我们要求字长 \(w\ge B\)。具体一点就是如果某个下标在单调栈内,就将数字对应那一位赋值为 \(1\)。查询块内 \((l,r)\) 时就查询 \(r\) 对应的单调栈状态满足 \(i\ge l\) 的第一个为 \(1\) 的数字即可。可以用 __builtin_ctz()
在 \(\mathcal O(1)\) 内计算。
2.2. 复杂度分析
朴素 \(\rm rmq\) 只用 \(\frac{n}{\log n}\) 个数,复杂度 \(\mathcal O(n)\)。单调栈预处理 \(\mathcal O(n)\),查询 \(\mathcal O(q)\)。
但是模板题还不如前缀后缀分块跑得快就是了。可能是我人丑常数大吧。
2.3. 代码
#include <cstdio>
#define print(x,y) write(x),putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while((s=getchar())>'9' or s<'0')
f|=(s=='-');
while(s>='0' and s<='9')
x=(x<<1)+(x<<3)+(s^48),
s=getchar();
return f?-x:x;
}
template <class T>
inline void write(const T x) {
if(x<0) {
putchar('-'),write(-x);
return;
}
if(x>9) write(x/10);
putchar(x%10^48);
}
namespace GenHelper {
unsigned z1,z2,z3,z4,b;
unsigned rand_() {
b=((z1<<6)^z1)>>13;
z1=((z1&4294967294U)<<18)^b;
b=((z2<<2)^z2)>>27;
z2=((z2&4294967288U)<<2)^b;
b=((z3<<13)^z3)>>21;
z3=((z3&4294967280U)<<7)^b;
b=((z4<<3)^z4)>>12;
z4=((z4&4294967168U)<<13)^b;
return (z1^z2^z3^z4);
}
}
void srand(unsigned x) {
using namespace GenHelper;
z1=x; z2=(~x)^0x233333333U;
z3=x^0x1234598766U; z4=(~x)+51;
}
int read() {
using namespace GenHelper;
int a=rand_()&32767;
int b=rand_()&32767;
return a*32768+b;
}
inline int Max(int x,int y) {int m=(x-y)>>31; return y&m|x&~m;}
#include <iostream>
using namespace std;
const int B=32,maxn=20000005;
int n,m,l,r,S[B+5],tp;
int a[maxn],lg[maxn/B+2];
int st[20][(int)9e5];
unsigned s;
struct Stack {
int sta[B+5],delta;
int QMax(int l,int r) {
return a[delta+l+__builtin_ctz(sta[r]>>l-1)];
}
void ins(int *A) {
tp=0;
for(int i=1;i<=B;++i) {
sta[i]=sta[i-1];
while(tp and A[S[tp]]<=A[i])
sta[i]^=(1<<S[tp]-1),--tp;
S[++tp]=i,sta[i]^=(1<<i-1);
}
}
} p[(int)9e5];
void init() {
int len=(n-1)/B+1; lg[0]=-1;
for(int i=1;i<=len;++i) {
st[0][i]=a[(i-1)*B+1];
for(int j=(i-1)*B+2;j<=i*B;++j)
st[0][i]=Max(st[0][i],a[j]);
lg[i]=lg[i>>1]+1;
p[i].ins(a+(i-1)*B),p[i].delta=(i-1)*B;
}
for(int j=1;(1<<j)<=len;++j)
for(int i=1;i+(1<<j)-1<=len;++i)
st[j][i]=Max(st[j-1][i],st[j-1][i+(1<<j-1)]);
}
int rmq(int l,int r) {
int d=lg[r-l+1];
return Max(st[d][l],st[d][r-(1<<d)+1]);
}
int calc() {
int a=(l-1)/B+1,b=(r-1)/B+1,ret=0;
if(a+1<=b-1) ret=rmq(a+1,b-1);
if(a==b) ret=p[a].QMax(l-(a-1)*B,r-(a-1)*B);
else ret=Max(ret,Max(p[a].QMax(l-(a-1)*B,B),p[b].QMax(1,r-(b-1)*B)));
return ret;
}
int main() {
n=read(9),m=read(9),s=read(9);
srand(s);
for(int i=1;i<=n;++i)
a[i]=read();
init();
unsigned long long ans=0;
while(m--) {
l=read()%n+1,r=read()%n+1;
if(l>r) swap(l,r);
ans+=calc();
}
print(ans,'\n');
return 0;
}
原文地址:https://www.cnblogs.com/AWhiteWall/p/15134821.html
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 【python-opencv】canny边缘检测
- python的日志模块案例
- 【python-opencv】图像金字塔
- Linux之inodes溢出问题
- volitale 怎么保证可见性
- 企业多人协同办公软件-Confluence 7.6体验
- 【python opencv】轮廓属性
- Go 语言学习之 reflect
- 【python opencv】轮廓更多属性
- Gin 学习之安装和快速启动
- 给兄弟讲bitCoins和blockchain
- Gin 学习之响应处理
- 【python opencv】直方图查找、绘制和分析
- Gin 学习之接收参数和读取 reader
- 【python opencv】二维直方图