BZOJ3745 / LOJ2809 Norma题解(线段树+单调栈)

题目:BZOJ3745/LOJ2809.
题目大意:给定一个长度为 n n n的序列 a i a_i ai,求:
∑ l = 1 n ∑ r = l n ( r − l + 1 ) max ⁡ i = l r { a i } min ⁡ i = l r { a i }    ( m o d    1 0 9 ) \sum_{l=1}^{n}\sum_{r=l}^{n}(r-l+1)\max_{i=l}^{r}\{a_i\}\min_{i=l}^{r}\{a_i\}\,\,(mod\,\,10^9) l=1nr=ln(rl+1)i=lmaxr{ai}i=lminr{ai}(mod109)

1 ≤ n ≤ 5 ∗ 1 0 5 , 1 ≤ a i ≤ 1 0 8 1\leq n\leq 5*10^5,1\leq a_i\leq 10^8 1n5105,1ai108,答案对 1 0 9 10^9 109取模.

分治做法.

首先,看到这种求所有子区间的 m i n min min m a x max max运算一下的套路题,马上就能知道它是分治 / / /线段树+单调栈.

考虑线段树+单调栈的做法,按照套路大力枚举右端点,单调栈维护每一个最值相同的区间,同时用线段树维护每个左端点的贡献.

这个时候我们的线段树应该需要同时维护信息 ( r − l + 1 ) (r-l+1) (rl+1)的区间加法,两个最值的区间修改,并要同时维护询问它们的乘积之和,这并不能用线段树直接维护.

考虑如何维护区间 ( r − l + 1 ) (r-l+1) (rl+1)的信息,我们发现枚举右端点为 i i i时,左端点 1 1 1的贡献为 i i i 2 2 2的贡献为 i − 1 i-1 i1直到 i i i的贡献为 1 1 1,这提示我们可以在一开始分两棵线段树维护.设 m x i mx_i mxi表示位置 i i i的最值乘积,那么一棵维护 m x i mx_i mxi,另一棵维护 m x i ( n − i + 1 ) mx_i(n-i+1) mxi(ni+1),查询的时候就可以用第二棵线段树在区间 [ 1 , i ] [1,i] [1,i]上的总贡献减去 ( n − i ) (n-i) (ni)乘上第一棵线段树在区间 [ 1 , i ] [1,i] [1,i]上的总贡献.

解决这个问题后考虑最值的维护,用单调栈的套路需要支持区间乘法和区间除法,但是这里的模数不是素数,无法做除法.不过我们注意到每一个需要除法的区间必然都是经过乘法后的撤销操作,所以给每个区间打两个永久化标记即可.

由于两棵树上的极值标记其实是一样的,所以我们不需要真的开两棵线段树,只需要同时在一棵线段树上维护四个标记就可以了,实测这样做可以减小一半的常数.

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn).

代码如下(注:下面这份代码在BZOJ上不可过):

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

#define Abigail inline void
typedef long long LL;

const int N=500000,mod=1000000000;

int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
int sub(int a,int b){return a-b<0?a-b+mod:a-b;}
int mul(int a,int b){return (LL)a*b%mod;}
void sadd(int &a,int b){a=add(a,b);}
void ssub(int &a,int b){a=sub(a,b);}
void smul(int &a,int b){a=mul(a,b);}

int n,a[N+9];
int tr[N*4+9][4];

void Pushup(int k){
  int ls=k<<1,rs=k<<1|1,vl=mul(tr[ls][0],tr[ls][1]),vr=mul(tr[rs][0],tr[rs][1]);
  tr[k][2]=add(mul(tr[ls][2],vl),mul(tr[rs][2],vr));
  tr[k][3]=add(mul(tr[ls][3],vl),mul(tr[rs][3],vr));
}

void Build(int L,int R,int k){
  tr[k][0]=tr[k][1]=1;
  if (L==R) return;
  int mid=L+R>>1;
  Build(L,mid,k<<1);Build(mid+1,R,k<<1|1);
}

void Change(int L,int R,int id,int v,int l,int r,int k){
  if (L==l&&R==r){
  	if (id==2) tr[k][3]=n-l+1;
  	tr[k][id]=v;
  	return;
  }
  int mid=l+r>>1;
  if (R<=mid) Change(L,R,id,v,l,mid,k<<1);
  else if (L>mid) Change(L,R,id,v,mid+1,r,k<<1|1);
    else Change(L,mid,id,v,l,mid,k<<1),Change(mid+1,R,id,v,mid+1,r,k<<1|1);
  Pushup(k);
}

int mx[N+9],mn[N+9],cmx,cmn;
int ans;

Abigail into(){
  scanf("%d",&n);
  for (int i=1;i<=n;++i)
    scanf("%d",&a[i]);
}

Abigail work(){
  Build(1,n,1);
  for (int i=1;i<=n;++i){
  	Change(i,i,2,1,1,n,1);
  	for (;cmx&&a[mx[cmx]]<=a[i];--cmx)
	  Change(mx[cmx-1]+1,mx[cmx],0,1,1,n,1);
	Change(mx[cmx]+1,i,0,a[i],1,n,1);
	mx[++cmx]=i;
  	for (;cmn&&a[mn[cmn]]>=a[i];--cmn)
  	  Change(mn[cmn-1]+1,mn[cmn],1,1,1,n,1);
  	Change(mn[cmn]+1,i,1,a[i],1,n,1);
  	mn[++cmn]=i;
  	sadd(ans,mul(sub(tr[1][3],mul(tr[1][2],n-i)),mul(tr[1][0],tr[1][1])));
  }
}

Abigail outo(){
  printf("%d\n",ans);
}

int main(){
  into();
  work();
  outo();
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值