[bzoj4540][Hnoi2016]序列——单调栈+莫队+RMQ dalaos' blogs Some Links

版权声明:欢迎大家转载,转载请标明出处。 https://blog.csdn.net/ylsoi/article/details/84895291

题目大意:

给定一个序列,每次询问一个区间[L,R]所有子区间的最小值之和。

思路:

考虑莫队如何转移,新增一个端点R,则增加的区间为[L…R-1,R],考虑这些区间新贡献的最小值。
我们把从R开始向左单调下降的序列给求出来,不难发现最小值是由区间内包含的最靠左一个在单调下降序列里的元素的值所决定的。
于是我们利用单调栈求出每一个元素前面第一个小于它的元素 p r e i pre_i ,并求出以这个元素结尾的所有区间的最小值的和 f i f_i ,不难发现 f i = f p r e i + ( i p r e i ) × a i f_i=f_{pre_i}+(i-pre_i)\times a_i ,递推求出来 f f 数组之后我们要求新增加一个点的贡献,只需要找出区间内最小的点的位置 m m ,对于左端点在m之前的,最小值都是 a m a_m ,区间左端点在 [ m + 1 , R ] [m+1,R] 的部分我们可以用 f i f m f_i-f_m 得到,这样就可以用RMQ O ( 1 ) O(1) 转移了。
但是这样还不够优秀,上面的做法提示我们,如果要求 i = L R m i n ( a i . . a R ) \sum_{i=L}^{R}min(a_i..a_R) 这个东西,可以用RMQ求出最小值的位置之后再利用 f f 数组相减来解决。
考虑对于一个区间[L,R],最小值的位置为 m m ,那么显然对于 [ m + 1 , R ] [m+1,R] 区间的所有点当它们为右端点时前缀最小值为 a m a_m ,所以通过 i = m + 1 R ( f i f m ) \sum_{i=m+1}^{R}(f_i-f_m) 可以得到左右端点都在[m+1,R]的子区间的权值和,同理可得左右端点都在[L,m-1]的子区间的权值和,对于包含m点的区间可以直接通过 ( m L + 1 ) × ( R m + 1 ) × a m (m-L+1)\times (R-m+1)\times a_m 计算出。
于是得到了这一题较优秀的做法,时间复杂度 O ( n log n + q ) O(n\log n +q)

#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
#define DREP(i,a,b) for(int i=a,i##_end_=b;i>=i##_end_;--i)
#define MREP(i,x) for(int i=beg[x],v;v=to[i],i;i=las[i])
#define debug(x) cout<<#x<<"="<<x<<endl
#define pii pair<ll,int>
#define fi first
#define se second
#define mk make_pair
#define pb push_back
typedef long long ll;

using namespace std;

void File(){
	freopen("bzoj4540.in","r",stdin);
	freopen("bzoj4540.out","w",stdout);
}

template<typename T>void read(T &_){
	T __=0,mul=1; char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')mul=-1;
		ch=getchar();
	}
	while(isdigit(ch))__=(__<<1)+(__<<3)+(ch^'0'),ch=getchar();
	_=__*mul;
}

const int maxn=1e5+10;
int n,m,Log[maxn];
ll a[maxn],f1[maxn],g1[maxn],f2[maxn],g2[maxn];
pii st[maxn][21];

int query(int l,int r){
	int d=Log[r-l+1];
	return min(st[l][d],st[r-(1<<d)+1][d]).se;
}

void init(){
	read(n); read(m);
	REP(i,1,n)read(a[i]);

	REP(i,2,n)Log[i]=Log[i/2]+1;
	REP(i,1,n)st[i][0]=mk(a[i],i);
	REP(j,1,20)REP(i,1,n-(1<<j)+1)
		st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);

	int tp;
	ll s[maxn];

	tp=0;
	REP(i,1,n){
		while(tp && a[s[tp]]>a[i])--tp;
		f1[i]=f1[s[tp]]+(i-s[tp])*a[i];
		s[++tp]=i; g1[i]=f1[i]+g1[i-1];
	}
	s[tp=0]=n+1;
	DREP(i,n,1){
		while(tp && a[s[tp]]>a[i])--tp;
		f2[i]=f2[s[tp]]+(s[tp]-i)*a[i];
		s[++tp]=i; g2[i]=f2[i]+g2[i+1];
	}
}

void work(){
	int l,r,p;
	ll ans;
	REP(i,1,m){
		read(l); read(r);
		p=query(l,r);
		ans=a[p]*(p-l+1)*(r-p+1);
		ans+=g1[r]-g1[p]-(r-p)*f1[p];
		ans+=g2[l]-g2[p]-(p-l)*f2[p];
		printf("%lld\n",ans);
	}
}

int main(){
	File();
	init();
	work();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/ylsoi/article/details/84895291