在开始之前先提一下RMQ与LCA这两个东西有什么关系
对于一个序列,对它构建出一颗笛卡尔树之后,两个点的LCA就是原序列中这两个点之间的最大值/最小值(取决于建树时的比较方式)
而对于一棵树,求出来他的欧拉序之后,两个点的LCA,就是两个点在欧拉序第一次出现的位置之间的深度最小值的点
所以LCA问题和RMQ问题是可以相互转换的
RMQ
RMQ 即区间最值问题,根据情景的不同,我们可以给出不同的方法
静态区间最值问题
ST表
ST表,是一个处理静态可重复贡献问题的利器,区间最值显然是可以重复贡献的。
记 \(st_{i,k}\) 表示原序列中 \([i,i+2^k-1]\) 中的最值,显然初始化是 \(st_{i,0}=a[i]\)
我们可以将 \([i,i+2^k-1]\) 拆分成 \([i,i+2^{k-1}-1]\) 和 \([i+2^{k-1},i+2^{k}-1]\) ,这两个区间中的最值显然是 \(st_{i,k-1}\) 和 \(st_{i+2^{k-1},k-1}\)
所以我们从小到大枚举 \(k\) ,用 \(st_{i,k-1}\) 和 \(st_{i+2^{k-1},k-1}\) 更新 \(st_{i,k}\)
for(int i=1;i<=n;i++)f[i][0]=read();
int t=log2(n);
for(int i=1;i<=t;i++){for(int k=1;k<=n-(1<<i)+1;k++){f[k][i]=max(f[k][i-1],f[k+(1<<(i-1))][i-1]);}
}//这里的i,k与上文中是反过来的
查询时,需要将 \([l,r]\) 拆成两个区间,使它们覆盖整个 \([l,r]\) ,可以发现 \(st_{l,\lfloor log_{2}{r-l+1}\rfloor}\) \(st_{r-2^{\lfloor log_{2}{r-l+1}\rfloor}+1,\lfloor log_{2}{r-l+1}\rfloor}\) 可以覆盖整个区间。
for(int i=1;i<=m;i++){int l,r;l=read();r=read();int k=log2(r-l+1);write(max(f[l][k],f[r-(1<<k)+1][k]));puts("");
}
预处理时间复杂度为 \(O(nlogn)\) ,单次查询复杂度为 \(O(1)\)
Method of Four Russians
就是人们常说的“四毛子”,这个算法在ST表的基础上进行了优化。
对序列进行分块,设块长为 \(B\) ,在块间建立ST表,时间复杂度为 \(O(\frac{N}{B}log{\frac{N}{B}})\),在块内建立ST表,时间复杂度为 \(O(\frac{N}{B}Blog{B})\).
查询时,若 \(l,r\) 在同一个块内,直接用块内的ST表查询,若 \(l,r\) 不在同一个块内,那么对于散块用块内的ST表查询,对于整块用块间的ST表查询。查询时间复杂度为 \(O(1)\)
当 \(B\) 取 \(O(log_2{n})\) 时,预处理时间复杂度是 \(O(nloglogn)\)
struct FR{int ST[N/len+5][log2(N/len)+5],st[N][log2(len)+5];struct block{int l,r,ma;}b[N/len+5];void init(){for(int i=1;i<=id(n);i++){b[i].l=b[i-1].r+1;b[i].r=i*len;b[i].ma=0;}b[id(n)].r=n;for(int i=1;i<=id(n);i++){int t=log2(b[i].r-b[i].l+1);for(int k=b[i].l;k<=b[i].r;k++)st[k][0]=a[k],b[i].ma=max(b[i].ma,a[k]);for(int k=1;k<=t;k++){for(int j=b[i].l;j+(1<<k)-1<=b[i].r;j++){st[j][k]=max(st[j][k-1],st[j+(1<<(k-1))][k-1]);} }}for(int i=1;i<=id(n);i++)ST[i][0]=b[i].ma;int t=log2(id(n));for(int i=1;i<=t;i++){for(int k=1;k+(1<<i)-1<=id(n);k++){ST[k][i]=max(ST[k][i-1],ST[k+(1<<(i-1))][i-1]);}}}int ask(int l,int r){int p=id(l),q=id(r);if(p==q){int t=log2(r-l+1);return max(st[l][t],st[r-(1<<t)+1][t]);}int x,y,z;int t=log2(b[p].r-l+1);x=max(st[l][t],st[b[p].r-(1<<t)+1][t]);t=log2(r-b[q].l+1);y=max(st[b[q].l][t],st[r-(1<<t)+1][t]);if(p+1>q-1)return max(x,y);t=log2(q-1-(p+1)+1);z=max(ST[p+1][t],ST[(q-1)-(1<<t)+1][t]);return max(x,max(y,z));}
}f;
但由于每次查询会使用3次ST表,所以常数会比直接建ST表大很多,在 \(n\) 较小的时候还是直接写ST表好
优化
当 \(l,r\) 不在一个块内时,考虑对于散块的查询,我们只需要考虑一个前缀或后缀的贡献就可以了,这样就可以将ST表调用次数降低到1次
O(N)-O(1)Rmq
四毛子的瓶颈在于当 \(l,r\) 在同一块时,没法做到快速处理。
这里给出一个基于状压的线性RMQ的做法
……