CF1117G Recursive Queries
题意
给出长度为\(n\)的排列,假设\(m_{l,r}\)为区间\([l,r]\)中最大值对应的下标,定义函数 \(f(l,r)=(r-l+1)+f(l,m_{l,r}-1)+f(m_{l,r}+1,r)\),若\(r<l\)则返回\(0\)。
有\(q\)次询问\(f(l_i,r_i)\)的值。
\(1 \leq n,q \leq 10^6\)
思路
单调栈预处理出\(l[i]\)表示\(i\)左边第一个比它大的位置\(+1\),\(r[i]\)表示右边第一个比它大的位置\(-1\)。
在询问\((L,R)\)中,对区间每个点考虑,当它成为\(max\)时区间长度贡献就是\(\text{min}(R,r[i])-\text{max}(L,l[i])+1\)
将两个分开计算,只讨论\(\text{max}(L,l[i])\),前面的同理。
开两个树状数组,一个记和,一个记个数。从小到大枚举右端点\(i\),加入\(i\)时,比较\(L,l[i]\)的大小:
- 当\(1 \leq L \leq l[i]\)时,\(l[i] \geq L\),区间\(+l[i]\)。
- 当\(l[i] < L \leq i\)时,\(L>l[i]\),区间加等差数列\(L\),区间个数\(+n1\)即可。
询问离线,对于右端点为\(i\)的询问\((L,i)\),此部分答案为\(-sum[L]-cnt[L] \times L\)
同样枚举一遍左端点即可,最后答案加上区间长度。
#include <bits/stdc++.h>
using std::vector;
typedef long long ll;
const int N=1000005;
int n,Q,a[N],l[N],r[N],st[N],tp;
vector<int> el[N],er[N];
ll s[N],ans[N],c[N];
int lowbit(int x){
return x&(-x);
}
void upd(ll *s,int x,int y){
while (x<=n){
s[x]+=y;
x+=lowbit(x);
}
}
ll getsum(ll *s,int x){
ll ret=0;
while (x){
ret+=s[x];
x-=lowbit(x);
}
return ret;
}
void add(ll *s,int l,int r,int x){
if (l>r) return;
upd(s,l,x),upd(s,r+1,-x);
}
struct query{
int l,r,id;
}q[N];
int main(){
scanf("%d%d",&n,&Q);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
for (int i=1;i<=n;i++){
while (tp && a[st[tp]]<=a[i]) tp--;
l[i]=st[tp]+1;
st[++tp]=i;
}
tp=0;
for (int i=n;i>=1;i--){
while (tp && a[st[tp]]<=a[i]) tp--;
r[i]=tp?st[tp]-1:n;
st[++tp]=i;
}
for (int i=1;i<=Q;i++) scanf("%d",&q[i].l),el[q[i].l].push_back(i);
for (int i=1;i<=Q;i++) scanf("%d",&q[i].r),er[q[i].r].push_back(i);
for (int i=1;i<=n;i++){
add(s,1,l[i],l[i]);
add(c,l[i]+1,i,1);
for (auto j:er[i]) ans[j]=-getsum(s,q[j].l)-getsum(c,q[j].l)*q[j].l;
}
memset(s,0,sizeof(s));
memset(c,0,sizeof(c));
for (int i=n;i>=1;i--){
add(s,r[i],n,r[i]);
add(c,i,r[i]-1,1);
for (auto j:el[i]) ans[j]+=getsum(s,q[j].r)+getsum(c,q[j].r)*q[j].r;
}
for (int i=1;i<=Q;i++) printf("%lld ",ans[i]+q[i].r-q[i].l+1);
}
* 生而自由 爱而无畏 *