题解-[HNOI2016]序列

题解-[HNOI2016]序列

[HNOI2016]序列

给定 \(n\)\(m\) 以及序列 \(a\{n\}\)。有 \(m\) 次询问,每次给定区间 \([l,r]\in[1,n]\),求

\[\sum_{l\le l'\le r'\le r}\min_{i=l'}^{r'}a_i \]

数据范围:\(1\le n,m\le 10^5\)\(|a_i|\le 10^9\)


蒟蒻要练习省选题,结果就遇到这道数据结构(好久没写数据结构题都忘光了)。结果正好遇到一道毒瘤题,于是蒟蒻来写篇题解。


这题是静态离线,令人想到 \(\texttt{ST}\) 表和莫队——真的就是他们。

将区间存下来排序,将左端点范围从小到大分 \(\sqrt n\) 份,左端点按份排序,右端点奇偶波浪排序。

friend int operator<(Moq x,Moq y){
	if(cas[x.l]!=cas[y.l]) return x.l<y.l;
	return (cas[x.l]&1)?x.r<y.r:x.r>y.r;
}

然后依次考虑排序后每一个区间询问,并通过上一个区间的答案递推,这就是莫队的思想


然后看这题:

如何通过上一个答案递推呢?需要先知道边界端点的贡献

即给定区间 \([l,r]\)\(l\) 端点的贡献\(=Ans[l,r]-Ans[l+1,r]\)

比如下面给出一个序列 \(a\{n\}(n=10)\)

4 4 5 3 6 2 1 5 6 9

给定区间 \([l,r]=[3,9]\),即 \(a_l\sim a_r\) 为:

5 3 6 2 1 5 6

所以左端点 \((l=3,a_l=5)\) 的贡献应该为

\[\sum_{j=l}^r\min\limits_{k=l}^j a_k \]

\[\min\{5\}+\min\{5,3\}+\min\{5,3,6\}+\cdots+\min\{5,3,6,2,1,5,6\} \]

\[=5+3+3+2+1+1+1 \]

令人想起单调栈,然而不可能每次询问 \(\Theta(n)\) 跑一遍。所以可以预处理:

通过维护单调栈,求出对于每个 \(a_i\)\(lw_i(lw_i<i)\) 表示 \(i\) 左边第一个比 \(a_i\) 小的元素的下标;\(rw_i(rw_i>i)\) 表示 \(i\) 右边第一个比 \(a_i\) 小的元素的下标(如果左边不存在比 \(a_i\) 小的元素,\(lw_i=0\);如果右边不存在比 \(a_i\) 小的元素,\(rw_i=n+1\))。

这样的话,就可以维护一个前缀和 \(lsm_i\) 后缀和 \(rsm_i\),其中

\[lsm_i=lsm_{lw_i}+a_i(i-lw_i),rsm_i=rsm_{rw_i}+a_i(rw_i-i) \]

然后上面的 \(5+3+3+2\) 就可以通过后缀和相减得,求右端点贡献时则用前缀和相减

因为对于区间 \([lw_i+1,i]\)\([i,rw_i-1]\)\(a_i\) 为其最小元素。

void Side(){
	lw.rz(n+7),rw.rz(n+7);
	a[0]=-inf;
	q.clear(),q.pb(0);
	for(int i=1;i<=n;i++){
		while(q.size()&&a[q.back()]>=a[i]) q.pop_back();
		lw[i]=q.back(),q.pb(i);
	}
	a[0]=0;
	a[n+1]=-inf;
	q.clear(),q.pb(n+1);
	for(int i=n;i>=1;i--){
		while(q.size()&&a[q.back()]>=a[i]) q.pop_back();
		rw[i]=q.back(),q.pb(i);
	}
	a[n+1]=0;
	lsm.rz(n+7),rsm.rz(n+7);
	for(int i=1;i<=n;i++) lsm[i]=lsm[lw[i]]+(lng)(i-lw[i])*a[i];
	for(int i=n;i>=1;i--) rsm[i]=rsm[rw[i]]+(lng)(rw[i]-i)*a[i];
}

最后一个问题:\(1+1+1\) 部分怎么办?

因为右端点是随机的,所以如果直接把左端点的贡献当做 \(rsm_l-rsm_r\),必然不妥。

考虑到假设区间 \([l,r]\)\(a_p\) 最小,那么必然

\[\forall j\in[p,r],\left(\min\limits_{k=l}^j a_k\right)=a_p \]

所以算 \(1+1+1\) 部分可以通过维护静态区间最小值下标\(\texttt{ST}\) 表)找到 \(p\),算出 \(a_p(r-p+1)\)

然后正因为 \(a_p\)\([l,r]\) 区间中最小的元素,所以 \([l,p-1]\) 段的左端点贡献也自然是 \(rsm_l-rsm_p\)

所以对于区间 \([l,r]\),左端点贡献为

\[rsm_l-rsm_p+a_p(r-p+1)\qquad [a_p=\min_{i=l}^r a_i] \]

右端点同理。

然后相邻两个区间之间就可以逐步转移了。

lng Mol(int l,int r){
	int p=getmin(l,r);
	return rsm[l]-rsm[p]+(lng)(r-p+1)*a[p];
}
lng Mor(int l,int r){
	int p=getmin(l,r);
	return lsm[r]-lsm[p]+(lng)(p-l+1)*a[p];
}
void runMo(){
	int L=1,R=0;
	for(int i=1;i<=m;i++){
		while(L>qu[i].l) res+=Mol(--L,R);
		while(R<qu[i].r) res+=Mor(L,++R);
		while(L<qu[i].l) res-=Mol(L++,R);
		while(R>qu[i].r) res-=Mor(L,R--);
		ans[qu[i].I]=res;
	}
}

时间复杂度 \(\Theta(m\sqrt n)\),空间复杂度 \(\Theta(n+m)\)


Code

我这个蒟蒻垃圾真是傻傻讲不清楚,还是放代码吧(要开 \(\texttt{long long}\))。

代码有点长,于是蒟蒻划分了一下。

#include <bits/stdc++.h>
using namespace std;

//Start
#define lng long long
#define db double
#define mk make_pair
#define pb push_back
#define fi first
#define se second
#define rz resize
const int inf=0x3f3f3f3f;
const lng INF=0x3f3f3f3f3f3f3f3f;

//Data
int n,m;
vector<int> a;

//Side
vector<int> lw,rw,q;
vector<lng> lsm,rsm;
void Side(){
	lw.rz(n+7),rw.rz(n+7);
	a[0]=-inf;
	q.clear(),q.pb(0);
	for(int i=1;i<=n;i++){
		while(q.size()&&a[q.back()]>=a[i]) q.pop_back();
		lw[i]=q.back(),q.pb(i);
	}
	a[0]=0;
	a[n+1]=-inf;
	q.clear(),q.pb(n+1);
	for(int i=n;i>=1;i--){
		while(q.size()&&a[q.back()]>=a[i]) q.pop_back();
		rw[i]=q.back(),q.pb(i);
	}
	a[n+1]=0;
	lsm.rz(n+7),rsm.rz(n+7);
	for(int i=1;i<=n;i++) lsm[i]=lsm[lw[i]]+(lng)(i-lw[i])*a[i];
	for(int i=n;i>=1;i--) rsm[i]=rsm[rw[i]]+(lng)(rw[i]-i)*a[i];//中间递推也要注意爆int
}

//ST
vector<int> lg;
vector<vector<int> > st;
int stcmp(int x,int y){
	return a[x]<a[y]?x:y;
}
void buildst(){
	lg.rz(n+7);
	for(int i=2;i<=n;i++) lg[i]=lg[i-1]+((1<<(lg[i-1]+1))<=i?1:0); //lg[i]=(int)log(i)
	st.rz(lg[n]+7);
	for(int j=0;j<=lg[n];j++) st[j].rz(n+7);
	for(int i=1;i<=n;i++) st[0][i]=i;
	for(int j=1;j<=lg[n];j++)
		for(int i=1;i<=n-(1<<(j-1));i++) st[j][i]=stcmp(st[j-1][i],st[j-1][i+(1<<(j-1))]);
}
int getmin(int l,int r){
	int len=lg[r-l+1];
	return stcmp(st[len][l],st[len][r-(1<<len)+1]);
}

//Moq
int sq;
lng res;
vector<int> cas;
vector<lng> ans;
struct Moq{
	int l,r,I;
	friend int operator<(Moq x,Moq y){
		if(cas[x.l]!=cas[y.l]) return x.l<y.l;
		return (cas[x.l]&1)?x.r<y.r:x.r>y.r;
	}
};
vector<Moq> qu;
void buildMo(){
	cas.rz(n+7),qu.rz(m+1);
	sq=sqrt(n);
	for(int i=1;i<=m;i++) scanf("%d%d",&qu[i].l,&qu[i].r),qu[i].I=i;
	for(int i=1;i<=n;i++) cas[i]=(i-1)/sq+1;
	sort(qu.begin(),qu.end());
}
lng Mol(int l,int r){
	int p=getmin(l,r);
	return rsm[l]-rsm[p]+(lng)(r-p+1)*a[p];
}
lng Mor(int l,int r){
	int p=getmin(l,r);
	return lsm[r]-lsm[p]+(lng)(p-l+1)*a[p];
}
void runMo(){
	int L=1,R=0;
	for(int i=1;i<=m;i++){
		while(L>qu[i].l) res+=Mol(--L,R);
		while(R<qu[i].r) res+=Mor(L,++R);
		while(L<qu[i].l) res-=Mol(L++,R);
		while(R>qu[i].r) res-=Mor(L,R--);
		ans[qu[i].I]=res;
	}
}

//Main
int main(){
	scanf("%d%d",&n,&m);
	a.rz(n+7);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	ans.rz(m+7);
	Side(),buildst(),buildMo(),runMo();
	for(int i=1;i<=m;i++)
		printf("%lld\n",ans[i]);
	return 0;
}

祝大家学习愉快!

猜你喜欢

转载自www.cnblogs.com/Wendigo/p/12634706.html