【bzoj4540】【HNOI2016】【序列】【莫队+st表】

Description

  给定长度为n的序列:a1,a2,…,an,记为a[1:n]。类似地,a[l:r](1≤l≤r≤N)是指序列:al,al+1,…,ar-
1
,ar。若1≤l≤s≤t≤r≤n,则称a[s:t]是a[l:r]的子序列。现在有q个询问,每个询问给定两个数l和r,1≤l≤r
≤n,求a[l:r]的不同子序列的最小值之和。例如,给定序列5,2,4,1,3,询问给定的两个数为1和3,那么a[1:3]有
6个子序列a[1:1],a[2:2],a[3:3],a[1:2],a[2:3],a[1:3],这6个子序列的最小值之和为5+2+4+2+2+2=17。

Input

  输入文件的第一行包含两个整数n和q,分别代表序列长度和询问数。接下来一行,包含n个整数,以空格隔开
,第i个整数为ai,即序列第i个元素的值。接下来q行,每行包含两个整数l和r,代表一次询问。

Output

  对于每次询问,输出一行,代表询问的答案。

Sample Input

5 5
5 2 4 1 3
1 5
1 3
2 4
3 5
2 5

Sample Output

28
17
11
11
17

HINT

1 ≤N,Q ≤ 100000,|Ai| ≤ 10^9

题解:

          考虑莫队,因为n比较大,我们需要O(1)转移.

          假设当前区间是(l,r)我们要转移到(l,r+1),那多出来的只是r+1产生的贡献.

          可以找到(l,r)中最靠右的最小值的位置t,那t左边到r+1的最小值都是a[t].

          考虑t右边的数,我们可以预处理一个前缀和s,s[i]表示以i为右端点的子序列的贡献,

          那t右边的数产生的贡献就是s[r+1]-s[t];其他移动更新方法同理.

          使用st表可以O(1)查询最小值.

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 100010 
using namespace std;
int a[N],st[N],l[N],r[N],bl[N],cnt,f[20][N],n,m,bk;
long long ans[N],sp[N],sa[N],temp;
struct use{int l,r,id;}q[N];
void pre(){
  int top;
  top=1;st[top]=1;st[0]=0;
  for (int i=2;i<=n;i++){
    while (top&&a[st[top]]>a[i]) top--;
    l[i]=st[top];st[++top]=i;
  }
  top=1;st[top]=n;st[0]=n+1;
  for (int i=n-1;i>=1;i--){
    while (top&&a[st[top]]>a[i]) top--;
    r[i]=st[top];st[++top]=i;
  }
  sp[1]=a[1];
  for (int i=2;i<=n;i++)
    sp[i]=sp[l[i]]+(long long)(i-l[i])*a[i];
  sa[n]=a[n];
  for (int i=n-1;i>=1;i--)
    sa[i]=sa[r[i]]+(long long)(r[i]-i)*a[i];
  for (int i=1;i<=n;i++) f[0][i]=i;
  for (int j=1;j<=18;j++)
    for (int i=1;i<=n;i++){
      if (a[f[j-1][i]]>=a[f[j-1][i+(1<<j>>1)]])      
        f[j][i]=f[j-1][i+(1<<j>>1)];
      else f[j][i]=f[j-1][i];
	}
}
bool cmp(use a,use b){
  if (bl[a.l]==bl[b.l]) return a.r<b.r;
  else return a.l<b.l;
}
int query(int l,int r){
  int t=log2(r-l+1);
  int x=f[t][l],y=f[t][r-(1<<t)+1];
  if (a[x]<a[y]) return x;
  else return y;
}
void dell(int l,int r){
  if (l>r) return;
  int t=query(l,r);
  temp-=(long long)(r-t+1)*a[t];
  temp-=sa[l]-sa[t];
}
void addl(int l,int r){
  if (l>r) return;
  int t=query(l,r);
  temp+=(long long)(r-t+1)*a[t];
  temp+=sa[l]-sa[t];
}
void delr(int l,int r){
  if (l>r) return;
  int t=query(l,r);
  temp-=(long long)(t-l+1)*a[t];
  temp-=sp[r]-sp[t];
}
void addr(int l,int r){
  if (l>r) return;
  int t=query(l,r);
  temp+=(long long)(t-l+1)*a[t];
  temp+=sp[r]-sp[t];
}
void solve(){
  int l=0,r=0;temp=0;
  for (int i=1;i<=m;i++){
  	while (l<q[i].l){dell(l,r);l++;}
  	while (l>q[i].l){l--;addl(l,r);}
  	while (r<q[i].r){r++;addr(l,r);}
  	while (r>q[i].r){delr(l,r);r--;}
    ans[q[i].id]=temp;
  }
}
int main(){
  scanf("%d%d",&n,&m);
  for (int i=1;i<=n;i++) scanf("%d",&a[i]);
  pre();
  for (int i=1;i<=m;i++){
    scanf("%d%d",&q[i].l,&q[i].r);
    q[i].id=i;
  }
  bk=sqrt(n);cnt=n/bk+(n%bk!=0);
  for (int i=1;i<=n;i++) bl[i]=(i-1)/bk+1;
  sort(q+1,q+m+1,cmp);
  solve();
  for (int i=1;i<=m;i++){
  	printf("%lld",ans[i]);
    if (i!=m) puts("");
  }
} 


          

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值