[HNOI2016]序列

题目描述

给定长度为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。

题解

这道题有点神。

我们令f[i]表示所有以i为右端点的答案,pre[i]表示i点前面第一个比它小的点。

那么f[i]-f[p]就是所有以i为右端点,左端点为(pre[i],I]的答案。

好了,现在我们考虑一个区间[l,r]的答案。

我们找到这个区间的最小点p,那么这个点的答案就是a[p]*(r-p+1)*(p-l+1)。

然后这个区间被分成了两个小区间。

先考虑右边区间的答案怎么算,左边同理。

我们其实要求的是(f[r]-f[p])+(f[r-1]-f[p])...+(f[p+1]-f[p])。

搞个前缀和就好了。

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#define N 100009
using namespace std;
typedef long long ll;
int p[20][N],rec[20][N],n,q,st[N],top,pre[N];
ll f[N],g[N],now,sum[N],_g[N],_f[N],a[N];
inline int rd(){
    int x=0;char c=getchar();bool f=0;
    while(!isdigit(c)){if(c=='-')f=1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}
inline int RMQ(int x,int y){
    int lo=log2(y-x+1);
    if(p[lo][x]<=p[lo][y-(1<<lo)+1])return rec[lo][x];else return rec[lo][y-(1<<lo)+1];
}
int main(){
    n=rd();q=rd();
    for(int i=1;i<=n;++i)a[i]=rd(),p[0][i]=a[i],rec[0][i]=i;
    for(int i=1;(1<<i)<=n;++i)
      for(int j=1;j+(1<<i)-1<=n;++j){
        p[i][j]=min(p[i-1][j],p[i-1][j+(1<<i-1)]);
        rec[i][j]=p[i-1][j]<=p[i-1][j+(1<<i-1)]?rec[i-1][j]:rec[i-1][j+(1<<i-1)];
      }
    ll now=0;
    for(int i=1;i<=n;++i){
        ll num=1;
        while(top&&(a[st[top]]>=a[i]))now-=sum[st[top]]*a[st[top]],num+=sum[st[top]],top--;
        st[++top]=i;
        now+=num*a[i];sum[st[top]]=num;
        f[i]=now;//cout<<f[i]<<" ";
    }
    //puts("");
    top=0;now=0;
    for(int i=n;i>=1;--i){
        ll num=1;
        while(top&&(a[st[top]]>=a[i]))now-=sum[st[top]]*a[st[top]],num+=sum[st[top]],top--;    
        st[++top]=i;
        now+=num*a[i];sum[st[top]]=num;
        _f[i]=now;//cout<<_f[i]<<" "; 
    }
//    puts("");
    for(int i=1;i<=n;++i)g[i]=g[i-1]+f[i];
    for(int i=n;i>=1;--i)_g[i]=_g[i+1]+_f[i];
    int l,r;
    while(q--){
        l=rd();r=rd();
        int p=RMQ(l,r);
        ll ans=1ll*a[p]*(p-l+1)*(r-p+1);
        ans+=(g[r]-g[p])-f[p]*(r-p);
        ans+=(_g[l]-_g[p])-_f[p]*(p-l);
        printf("%lld\n",ans);
    }
    return 0;
}

转载于:https://www.cnblogs.com/ZH-comld/p/10514403.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值