[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 类,分别计算它们的贡献。假设 i 覆盖的区间是 [li,ri],询问的区间是 [L,R]

  1. li<L,R<ri,这样的点至多只有一个(区间的最小值),直接算它的贡献。
  2. li<L,Lri<R,这样的点的贡献是与 L 有关的一次函数,于是可以二维数点算出系数。
  3. L<liRi,R<ri,计算方法同上。
  4. Lli,riR,贡献是常数,还是二维数点。

然后离线树状数组,比做法 1 还是简单多了。

把代码搞丢了,懒得重新写了

做法 3

最简单,但是每次都要搞忘的做法。

我们可以用前缀和快速算出 i=lrai×(rili+1),这显然算多了,考虑把多了的部分减掉。

多了的部分有哪些呢,首先找到区间的最小值所在的位置 pp 的左右端点都可能超出了查询的区间,直接把它多做的贡献减去。
除此之外,还有一些位置的左端点会超出查询区间,一些位置的右端点会超出查询区间。

然后可以发现,左端点超出区间的位置一定是 [L,p) 的前缀最小值,右端点则是 (p,R] 的后缀最大值。我们对每个位置 i 找出右边第一个比自己大的位置作为父亲 fai,于是我们建出了一棵树,而左端点超出区间的位置一定是从 Lp 的这一段链,于是可以直接前缀和维护。

放一份 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));
	}
}
posted @   蒟蒻_william555  阅读(68)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· .NET Core 中如何实现缓存的预热?
· 三行代码完成国际化适配,妙~啊~
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
点击右上角即可分享
微信分享提示