[HNOI2016]序列
比较典的一道题,经常作为数据结构题的子问题出现,做法也多种多样,但是每次都会搞忘最简单的 ST 表+前缀和的做法,跑去写二维数电,很憨憨,所以必须来记录一下!
题意
给定一个序列,多次询问一个区间的所有子区间的区间最大值之和。
做法 1
当年模拟赛的时候场上写的代码,很麻烦,但是场切了就是场切了。
建出笛卡尔树,把区间所有点分成 4 类:子树的 左端点/右端点 在/不在 区间内。我们相当于要建出只包含区间内的数的虚树,然后计算答案,然后就要维护 最左边/最右边 的链算贡献。
大体上是这样的,做法比较答辩,就不细说了。
#include<bits/stdc++.h>
using namespace std;
const int BS=1<<17;
char buf[BS],*S,*T;
inline char gc(){
if(S==T)T=(S=buf)+fread(buf,1,BS,stdin);
return S!=T?*(S++):EOF;
}
inline int in(){
int x=0,f=1;char c=gc();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
while(c>='0'&&c<='9')x=x*10+c-48,c=gc();
return x*f;
}
typedef long long ll;
const int N=1e5+5;
int n,q,rt;
int a[N];
int st[N],tp;
int lp[N],rp[N],ls[N],rs[N];
vector<int> e[N];
ll f[N],fl[N],fr[N];
ll g1[N],g2[N],h1[N],h2[N];
int sz[N],fa[N],top[N],dep[N],son[N];
void dfs1(int x){
dep[x]=dep[fa[x]]+1;
sz[x]=1;
lp[x]=rp[x]=x;
for(int y:e[x]){
fa[y]=x;
dfs1(y);
lp[x]=min(lp[x],lp[y]);
rp[x]=max(rp[x],rp[y]);
f[x]+=f[y];
sz[x]+=sz[y];
if(sz[y]>sz[son[x]])son[x]=y;
}
f[x]+=1ll*a[x]*(x-lp[x]+1)*(rp[x]-x+1);
}
void dfs2(int x){
if(son[x]){
top[son[x]]=top[x];
dfs2(son[x]);
}
for(int y:e[x]){
if(y==son[x])continue;
top[y]=y;
dfs2(y);
}
}
inline int lca(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]>dep[top[y]])x=fa[top[x]];
else y=fa[top[y]];
}
return dep[x]<dep[y]?x:y;
}
void dfs3(int x){
int szl=x-lp[x]+1,szr=rp[x]-x+1;
ll val=1ll*a[x]*(x-lp[x]+1)*(rp[x]-x+1);
fl[x]+=val,fr[x]+=val;
for(int y:e[x]){
g1[y]=g1[x],g2[y]=g2[x];
h1[y]=h1[x],h2[y]=h2[x];
fl[y]=fl[x],fr[y]=fr[x];
if(y<x){
g1[y]-=1ll*szr*a[x],g2[y]+=1ll*(x+1)*szr*a[x];
fr[y]+=f[rs[x]];
}
if(y>x){
h1[y]+=1ll*szl*a[x],h2[y]+=1ll*(1-x)*szl*a[x];
fl[y]+=f[ls[x]];
}
dfs3(y);
}
}
int main(){
n=in(),q=in();
for(int i=1;i<=n;i++)a[i]=in();
for(int i=1;i<=n;i++){
int lst=0;
while(tp&&a[st[tp]]>a[i])rs[st[tp]]=lst,lst=st[tp],tp--;
ls[i]=lst;
st[++tp]=i;
}
while(tp>1)rs[st[tp-1]]=st[tp],tp--;
rt=st[1];
for(int i=1;i<=n;i++){
if(ls[i])e[i].push_back(ls[i]);
if(rs[i])e[i].push_back(rs[i]);
}
dfs1(rt);
top[rt]=rt;
dfs2(rt);
dfs3(rt);
while(q--){
int x=in(),y=in(),z=lca(x,y);
ll ans=f[z];
ans-=fl[x]-fl[z]+fr[y]-fr[z]+1ll*(z-lp[z]+1)*(rp[z]-z+1)*a[z]+f[ls[x]]+f[rs[y]];
ll k=g1[x]-g1[ls[z]],b=g2[x]-g2[ls[z]];
if(x!=z)ans+=k*x+b;
k=h1[y]-h1[rs[z]],b=h2[y]-h2[rs[z]];
if(y!=z)ans+=k*y+b;
if(x!=z)ans+=1ll*a[x]*(rp[x]-x+1);
if(y!=z)ans+=1ll*a[y]*(y-lp[y]+1);
ans+=1ll*a[z]*(z-x+1)*(y-z+1);
printf("%lld\n",ans);
}
return 0;
}
做法 2
这是第二次遇到这个子问题的时候想到的方法。
还是把所有点分为 4 类,分别计算它们的贡献。假设 覆盖的区间是 ,询问的区间是 。
- ,这样的点至多只有一个(区间的最小值),直接算它的贡献。
- ,这样的点的贡献是与 有关的一次函数,于是可以二维数点算出系数。
- ,计算方法同上。
- ,贡献是常数,还是二维数点。
然后离线树状数组,比做法 1 还是简单多了。
把代码搞丢了,懒得重新写了
做法 3
最简单,但是每次都要搞忘的做法。
我们可以用前缀和快速算出 ,这显然算多了,考虑把多了的部分减掉。
多了的部分有哪些呢,首先找到区间的最小值所在的位置 , 的左右端点都可能超出了查询的区间,直接把它多做的贡献减去。
除此之外,还有一些位置的左端点会超出查询区间,一些位置的右端点会超出查询区间。
然后可以发现,左端点超出区间的位置一定是 的前缀最小值,右端点则是 的后缀最大值。我们对每个位置 找出右边第一个比自己大的位置作为父亲 ,于是我们建出了一棵树,而左端点超出区间的位置一定是从 到 的这一段链,于是可以直接前缀和维护。
放一份 sthing 的代码
#include <iostream>
#include <cstdio>
const int nn = 1e5 + 5;
typedef long long ll;
int n, m, sta[nn], top, l[nn], r[nn], log[nn], st[nn][17];
ll fl[nn], fr[nn], a[nn], gl[nn], gr[nn];
int min(int p, int q) { return a[p] < a[q] ? p : q; }
int query(int l, int r) {
int tmp = log[r - l + 1];
return min(st[l][tmp], st[r - (1 << tmp) + 1][tmp]);
}
int main() {
scanf("%d %d", &n, &m); log[0] = -1;
for (int i = 1; i <= n; i++) {
scanf("%lld", a + i);
log[i] = log[i >> 1] + 1;
st[i][0] = i;
}
for (int i = 1; (1 << i) <= n; i++)
for (int j = 1; j + (1 << i) - 1 <= n; j++)
st[j][i] = min(st[j][i - 1], st[j + (1 << i - 1)][i - 1]);
a[n + 1] = -1e15;
for (int i = 1; i <= n + 1; i++) {
while (top && a[sta[top]] > a[i]) r[sta[top--]] = i;
l[i] = sta[top]; sta[++top] = i;
}
for (int i = 1; i <= n; i++) {
fr[i] = a[i] * (i - l[i]) + fr[l[i]];
gr[i] = gr[i - 1] + fr[i];
}
for (int i = n; i >= 1; i--) {
fl[i] = a[i] * (r[i] - i) + fl[r[i]];
gl[i] = gl[i + 1] + fl[i];
}
while (m--) {
int l, r, p; scanf("%d %d", &l, &r); p = query(l, r);
printf("%lld\n", a[p] * (r - p + 1) * (p - l + 1) + gr[r] - gr[p] - fr[p] * (r - p) + gl[l] - gl[p] - fl[p] * (p - l));
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· .NET Core 中如何实现缓存的预热?
· 三行代码完成国际化适配,妙~啊~
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?