bzoj4540: [Hnoi2016]序列

传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4540

思路:又是莫队....

我们发现左右端点移动时,只会增加或删除某个点开头或结尾的区间

先考虑右端点从r移动到r+1

令p为[l,r]中最小值的位置

那么它会对新加的区间中的p-l+1个区间产生a[p]的贡献

另一些左端点在[p+1,r],右端点是r+1的区间怎么统计呢?

首先用单调栈求出l[i],r[i]表示向左第一个小于a[i]的数的位置,向右第一个小于等于a[i]的数的位置

(注意:一个是小于,一个是小于等于,这样是防止重复计算)

预处理出两个类似前缀和的数组sl[i]和sr[i]

其中sl[i]=sl[l[i]]+(i-l[i])*a[i],sr同理

表示以这个为结束的答案的前缀和

左端点在l[i]及以前的答案就是sl[l[i]],左端点在[l[i],i]之间的最小值肯定是a[i],所以答案加上(i-l[i])*a[i]


那么左端点在[p+1,r],右端点是r+1的区间对答案的贡献就是sl[r+1]-sl[p]


那么移动后,答案加上(p-l+1)*a[i]+sl[r]-sl[p]即可

左端点移动及删除类似

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
typedef long long ll;
const int maxn=100010,maxk=22,inf=1e9;
using namespace std;
int n,Q,sz,top,a[maxn],st[maxn][maxk],pw[maxk],stk[maxn],L[maxn],R[maxn],bel[maxn],lg[maxn];ll sl[maxn],sr[maxn],now,ans[maxn];
struct quer{int l,r,id;}q[maxn];
bool operator <(quer a,quer b){return bel[a.l]==bel[b.l]?a.r<b.r:bel[a.l]<bel[b.l];}
int getmin(int x,int y){return a[x]<a[y]?x:y;}
int calc(int x,int y){
	if (x>y) swap(x,y);
	int l=lg[y-x+1];
	//printf("x=%d y=%d log=%d minpos%d\n",x,y,l,getmin(st[x][l],st[y-pw[l]+1][l]));
	return getmin(st[x][l],st[y-pw[l]+1][l]);
}

void modifyl(int l,int r,int op){
	int p=calc(l,r);
	ll tmp=1ll*a[p]*(r-p+1)+sr[l]-sr[p];
	now+=op*tmp;
}

void modifyr(int l,int r,int op){
	int p=calc(l,r);
	ll tmp=1ll*a[p]*(p-l+1)+sl[r]-sl[p];
	now+=op*tmp;
}

void work(){
	sort(q+1,q+1+Q);now=a[1];
	for (int l=1,r=1,i=1;i<=Q;i++){
		//printf("i=%d\n",i);
		for (;r<q[i].r;r++) modifyr(l,r+1,1);
		for (;l>q[i].l;l--) modifyl(l-1,r,1);
		for (;r>q[i].r;r--) modifyr(l,r,-1);
		for (;l<q[i].l;l++) modifyl(l,r,-1);
		ans[q[i].id]=now;
	}
	for (int i=1;i<=Q;i++) printf("%lld\n",ans[i]);
}

int main(){
	//freopen("sequence1.in","r",stdin);freopen("sequence.out","w",stdout);
	scanf("%d%d",&n,&Q),sz=sqrt(n),lg[0]=-1;
	pw[0]=1;for (int i=1;i<=18;i++) pw[i]=pw[i-1]<<1;
	for (int i=1;i<=n;i++) lg[i]=lg[i>>1]+1,bel[i]=(i-1)/sz+1;
	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
	for (int i=1;i<=n;i++) st[i][0]=i;
	for (int j=1;j<=18;j++)
		for (int i=1;i<=n;i++){
			st[i][j]=st[i][j-1];
			if (i+pw[j-1]<=n) st[i][j]=getmin(st[i][j-1],st[i+pw[j-1]][j-1]);
			//printf("%d ",st[i][j]);
		}
	for (int i=1;i<=n;i++){
		while (top&&a[stk[top]]>=a[i]) R[stk[top--]]=i;
		stk[++top]=i;
	}
	while (top) R[stk[top--]]=n+1;
	for (int i=n;i;i--){
		while (top&&a[stk[top]]>a[i]) L[stk[top--]]=i;
		stk[++top]=i;
	}
	while (top) L[stk[top--]]=0;
	
	//for (int i=1;i<=n;i++) printf("i=%d L=%d R=%d\n",i,L[i],R[i]);
	for (int i=n;i;i--) sr[i]=sr[R[i]]+1ll*(R[i]-i)*a[i];
	for (int i=1;i<=n;i++) sl[i]=sl[L[i]]+1ll*(i-L[i])*a[i];
	//for (int i=1;i<=n;i++) printf("i=%d sl=%lld sr=%lld\n",i,sl[i],sr[i]);
	for (int i=1;i<=Q;i++) scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i;
	work();
	return 0;
}
/*
5 5
5 2 4 1 3
1 5
1 3
2 4
3 5
2 5

*/



你可能感兴趣的:(bzoj4540: [Hnoi2016]序列)