RMQ 问题 / ST 表:静态区间求最值。
实现(以最大值为例):
-
倍增 dp,预处理 \(st_{i,j}\) 表示区间 \([i,i+2^j-1]\) 内的最大值,我们有转移方程:
\[st_{i,j}=\max(st_{i,j-1},st_{i+2^{j-1},j-1}) \]相当于是把 \([i,i+2^{j-1}-1]\) 与 \([i+2^{j-1},i+2^j-1]\) 这两段区间的最大值拼了起来。
(倍增中常把 \(2^j\) 拆分为 \(2^{j-1}+2^{j-1}\))
-
对于每组询问 \([l,r]\),找到大于等于区间长度一半的二次幂(实际上易证这就是 \(\log_2 \ r-l+1\)),从 \(l,r\) 分别找,从而覆盖 \([l,r]\)。
\[\begin{cases} j=\log_2 r-l+1\\ ans=\max(dp_{l,j},dp_{r-2^j+1,r}) \end{cases} \]
P3865
板子。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5,M=31;
int n,m,a[N];
int dp[N][M];
void STinit(){
for(int i=1;i<=n;i++)
dp[i][0]=a[i];
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
dp[i][j]=max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}
int STquery(int l,int r){
int t=log(r-l+1)/log(2);
return max(dp[l][t],dp[r-(1<<t)+1][t]);
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
STinit();
while(m--){
int l,r; cin>>l>>r; cout<<STquery(l,r)<<'\n';
}
return 0;
}
CF689D
首先发掘性质,\(\max^{r}_{i=l} b_i\) 显然单调不减, \(\min^{r}_{i=l} a_i\) 显然单调不增,这说明可以只枚举起点,二分终点,从而确定区间。
接着我们发现两者相等的必定为一段连续区间,于是我们就二分查找满足 \(\max^{r}_{i=l} b_i \le \min^{r}_{i=l} a_i\) 的最大的 \(l1\),以及满足 \(\max^{r}_{i=l} b_i \ge \min^{r}_{i=l} a_i\) 的最大的 \(r2\),这个 \(l\) 对答案的贡献即为 \(l1-r2+1\)。
注意最后要在判断一次 \(\max^{r}_{i=l} b_i = \min^{r}_{i=l} a_i\),因为有可能找不到 \(l1,r2\)。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5,M=31;
int n,m,ans;
int a[N],b[N];
int dpa[N][M],dpb[N][M];
void STinitA(){
for(int i=1;i<=n;i++)
dpa[i][0]=a[i];
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
dpa[i][j]=max(dpa[i][j-1],dpa[i+(1<<(j-1))][j-1]);
}
void STinitB(){
for(int i=1;i<=n;i++)
dpb[i][0]=b[i];
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
dpb[i][j]=min(dpb[i][j-1],dpb[i+(1<<(j-1))][j-1]);
}
int STqueryA(int l,int r){
int t=log(r-l+1)/log(2);
return max(dpa[l][t],dpa[r-(1<<t)+1][t]);
}
int STqueryB(int l,int r){
int t=log(r-l+1)/log(2);
return min(dpb[l][t],dpb[r-(1<<t)+1][t]);
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) cin>>b[i];
STinitA(),STinitB();
for(int i=1;i<=n;i++){
int l1=i-1,r1=n+1;
int l2=i-1,r2=n+1;
while(l1+1<r1){
int mid=(l1+r1)>>1;
if(STqueryA(i,mid)<=STqueryB(i,mid))
l1=mid;
else
r1=mid;
}
while(l2+1<r2){
int mid=(l2+r2)>>1;
if(STqueryA(i,mid)>=STqueryB(i,mid))
r2=mid;
else
l2=mid;
}
if(STqueryA(i,l1)==STqueryB(i,l1)&&STqueryA(i,r2)==STqueryB(i,r2)) ans+=l1-r2+1;
}
cout<<ans;
return 0;
}
LOJ #10121
首先,容易发现一条性质:若有一个确定的最长完美序列 \([i,j]\),则一定不存在完美序列 \([k,j]\),其中 \(k\) 满足 \(k<i\)。这说明完美序列存在单调性。
然后对于一组询问 \([l,r]\),考虑进行分割,哪部分可以更好算出贡献,就以它进行分割。
在这里,我们发现可能存在一个点 \(p\),使得以区间 \([l,p)\) 内的点为右端点的完美序列的左端点 \(L_1\) 满足 \(L_1 < l\),而以区间 \([p,r]\) 内的点为右端点的完美序列的左端点 \(L_2\) 满足 \(l \le L_2 \le r\)。
因为单调性的存在,所以 \(p\) 可以二分查找得到。
计算 \([l,p)\) 内的贡献是简单的,即为 \((p-1)-L_1+1=p-L_1\)。
对于每个 \(i \in [p,r]\),它贡献为 \(i-L_2+1\),用 ST 表维护这个式子的 \(\max\) 即可。
于是 \([l,r]\) 中的最长完美序列的长度即为 \(\max(p-L_1,\operatorname{query}(p,r))\)(\(\operatorname{query}\) 为 ST 表中的查询)。
接下来就是 \(L_1,L_2\) 的维护。
令 \(last_{a_i}\) 表示 \(a_i\) 上一次出现的位置,\(st_i\) 表示以 \(i\) 为右端点的最长完美序列的左端点。
显然有转移:
\[st_i = \max(st_{i-1},last_i+1) \](可以继承 \(st_{i-1}\) 是因为有单调性)
最后注意下标从 \(0\) 开始,以及 \(a_i\) 可能为负,\(last_{a_i}\) 要用 \(\operatorname{map}\) 或者平移也行。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5,M=31;
int n,m,a[N];
int dp[N][M];
int st[N];
int l,r;
map<int,int> last;
void STinit(){
for(int i=1;i<=n;i++)
dp[i][0]=i-st[i]+1;
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
dp[i][j]=max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}
int STquery(int l,int r){
int t=log(r-l+1)/log(2);
return max(dp[l][t],dp[r-(1<<t)+1][t]);
}
int bin(int l,int r){
int lt=l-1,rt=r+1;
while(lt+1<rt){
int mid=(lt+rt)>>1;
if(st[mid]<l) lt=mid;
else rt=mid;
}
return rt;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++){
st[i]=max(st[i-1],last[a[i]]+1);
last[a[i]]=i;
}
STinit();
while(m--){
//int l,r;
cin>>l>>r;
l++,r++;
int p=bin(l,r);
if(p>r) cout<<r-l+1<<'\n';
else cout<<max(p-l,STquery(p,r))<<'\n';
}
//cout<<ans;
return 0;
}
CF1142B
对于 \([l,r]\) 中的每个数 \(i\),我们都尝试将其作为 \(p\) 的循环移位的终点,
从 \(i\) 不断往前跳 \(n-1\) 次,记录它在 \(a_i\) 能跳到的点 \(b_i\),
把 \(b_i\) 扔进 ST 表中,对于每组询问 \([l,r]\) 取 \(\max\),
若这个值 \(\ge l\),则说明存在,否则不存在。
具体实现细节见 code。
code
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5,M=31;
int n,m,q;
int p[N],a[N];
int pos[N],last[N],b[N],st[N][M];
void STinit1(){
//memset(st,0,sizeof st);
for(int j=1;j<=25;j++)
for(int i=1;i<=m;i++)
st[i][j]=st[st[i][j-1]][j-1];
}
void STinit2(){
memset(st,0,sizeof st);
for(int i=1;i<=m;i++) st[i][0]=b[i];
for(int j=1;(1<<j)<=m;j++)
for(int i=1;i+(1<<j)-1<=m;i++)
st[i][j]=max(st[i][j-1],st[i+(1<<j-1)][j-1]);
}
int STjump(int x){
int now=0;
for(int i=25;i>=0;i--){
if(now+(1<<i)<n)
x=st[x][i],
now+=(1<<i);
}
return x;
}
int STquery(int l,int r){
int t=log2(r-l+1);
return max(st[l][t],st[r-(1<<t)+1][t]);
}
int main(){
//last[x]表示x在a中的下标
//pos[x]表示x在p中的下标
//st[i][j]表示i跳回2^j步所到的点
cin>>n>>m>>q;
for(int i=1;i<=n;i++)
cin>>p[i],pos[p[i]]=i;
for(int i=1;i<=m;i++){
cin>>a[i];
if(pos[a[i]]==1) st[i][0]=last[p[n]];
else st[i][0]=last[p[pos[a[i]]-1]];
last[a[i]]=i;
}
STinit1();
for(int i=1;i<=m;i++)
b[i]=STjump(i);
STinit2();
while(q--){
int l,r; cin>>l>>r;
cout<<(STquery(l,r)>=l?1:0);
}
return 0;
}