HNOI2016 序列 题解

HNOI2016 序列 题解

我做离线版本时往了偏序方向想,但是发现非常麻烦。直到看到了在线版本的容斥做法,发现既好写又跑得快。

首先考虑容斥,我们不妨把一个询问 [L,R] 中最小值的位置 pos 求出来。

  • 子区间跨过 pos,贡献即 (posL+1)×(Rpos+1)×apos
  • 子区间在 pos 左侧。
  • 子区间在 pos 右侧。

对于子区间在 pos 左侧和右侧是等价的,我们讨论在左侧的情况,可以考虑容斥,经过尝试可以发现一下的构造方式是可行的。

以下我们记 [l,r][L,R] 表示左端点在 [l,r],右端点在 [L,R] 的所有区间最小值的和。

那么

[L,pos1][L,pos1]=[L,n][L,n][pos,n][pos,n][L,pos1][pos,n]

在这里我们运用了 [L,pos1][pos,n] 一定跨过 pos 的性质来求。我们看具体怎么做。

fi=[i,n][i,n]。可以递推求 fi,有 fi=fi+1+[i,i][i,n]

怎么求 [i,i][i,n],考虑还可以递推,设 Fi=[i,i][i,n]

考虑把最小值为 ai 的区间一并贡献,那么设 ri 表示 i 右侧第一个比 ai 小的位置,对于 ri 及其右侧的贡献就是 Fri,所以就有

Fi=(rii)×ai+Fri

ri 可以从右往左扫一遍用单调栈求。

我们再来看 [L,pos1][pos,n],前面说了要运用它跨过 pos 的性质,由于 pos[L,R] 内最小的位置,所以 [L,pos1] 都不大于 pos,于是就有等价

[L,pos1][pos,n]=(posL)×[pos][pos,n]=(posL)×Fpos

那么这道题就做完了,瓶颈在于 RMQ,可以使用 ST 表做到 O(nlogn+Q),或者使用 O(n)O(1) RMQ 做到 O(n+Q)

代码难度较低,细节不多。

AC 代码:

using gen::init;
using gen::input;
using gen::output;
using gen::finish;
const int N=1e5+5;
int a[N];
pair<int,int> mn[17][N];
int get(int l,int r){
int len=r-l+1;
return min(mn[__lg(len)][l],mn[__lg(len)][r-(1<<__lg(len))+1]).second;
}
int stk[N],top=0;
int L[N],R[N];
ll f[N],F[N],g[N],G[N];
signed main(){
read(n,Q,type);
fo(i,1,n){
read(a[i]);
mn[0][i]={a[i],i};
while(top&&a[stk[top]]>=a[i])--top;
L[i]=stk[top];
stk[++top]=i;
F[i]=F[L[i]]+(ll)(i-L[i])*a[i];
f[i]=f[i-1]+F[i];
}
stk[top=0]=n+1;
fd(i,n,1){
while(top&&a[stk[top]]>=a[i])--top;
R[i]=stk[top];
stk[++top]=i;
G[i]=G[R[i]]+(ll)(R[i]-i)*a[i];
g[i]=g[i+1]+G[i];
}
fo(i,1,__lg(n)){
fo(j,1,n-(1<<i)+1){
mn[i][j]=min(mn[i-1][j],mn[i-1][j+(1<<i-1)]);
}
}
if(type==1)init();
while(Q--){
int l,r;
if(type==1)input(l,r);
else read(l,r);
ull ans=0;
int pos=get(l,r);
ans=(ull)a[pos]*(pos-l+1)*(r-pos+1);
ans+=g[l]-g[pos]-(ll)G[pos]*(pos-l)+f[r]-f[pos]-F[pos]*(r-pos);
output(ans);
}
finish();
return 0;
}
posted @   dengchengyu  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示