P3246 [HNOI2016]序列(莫队+单调栈+ST表)

[HNOI2016]序列

Tea神题解 Kelin神题解

对于莫队算法最主要的是如何快速算出 [ l , r ] → [ l , r + 1 ] [l,r]\to[l,r+1] [l,r][l,r+1]对答案的贡献的变化。
当询问区间发生上述变化时 [ l , r ] → [ l , r + 1 ] [l,r]\to [l,r+1] [l,r][l,r+1]不难发现子区间增加这些: [ l , r + 1 ] , [ l + 1 , r + 1 ] , … , [ r , r + 1 ] , [ r + 1 , r + 1 ] [l,r+1],[l+1,r+1],\dots,[r,r+1],[r+1,r+1] [l,r+1],[l+1,r+1],,[r,r+1],[r+1,r+1]总共 r − l + 2 r-l+2 rl+2个子区间 [ l → r + 1 , r + 1 ] [l\to r+1,r+1] [lr+1,r+1],需要求出他们对答案的贡献即每个区间的区间最小值之和。

考虑求出 [ l , r + 1 ] [l,r+1] [l,r+1]区间最小值的位置是 p p p,不难得知这些子区间 [ l → p , r + 1 ] [l\to p,r+1] [lp,r+1]的最小值都是 a p a_p ap,这部分对答案的贡献是 a p × ( p − l + 1 ) a_p×(p-l+1) ap×(pl+1)这里可以用ST表预处理快速求出。

而对于这些子区间 [ p + 1 → r + 1 , r + 1 ] [p+1\to r+1,r+1] [p+1r+1,r+1]
预处理 f i f_i fi:表示以 i i i为区间右端点的最小值之和
f i = f L i + a i × ( i − L i ) f_i=f_{L_i}+a_i×(i-L_i) fi=fLi+ai×(iLi)
L i L_i Li表示 i i i左边第一个小于等于 a i a_i ai的位置。(可以用单调栈预处理)

不难发现一定有 x x x使得 L x = p L_x=p Lx=p于是有 f r + 1 = a r + 1 × ( r + 1 − L r + 1 ) + ⋯ + a x × ( x − p ) + f p f_{r+1}=a_{r+1}×(r+1-L_{r+1})+\dots+a_x×(x-p)+f_p fr+1=ar+1×(r+1Lr+1)++ax×(xp)+fp
于是子区间 [ p + 1 → r + 1 , r + 1 ] [p+1\to r+1,r+1] [p+1r+1,r+1]对答案的贡献是 f r + 1 − f p f_{r+1}-f_p fr+1fp

对于 [ l , r ] → [ l − 1 , r ] [l,r]\to[l-1,r] [l,r][l1,r]可以效仿移动右端点的方式预处理 R i R_i Ri以及 g i g_i gi即可。

时间复杂度 O ( n log ⁡ n + n n ) O(n\log n+n\sqrt{n}) O(nlogn+nn )

注意莫队首先移动右端点因为初始化时 l = 1 , r = 0 l=1,r=0 l=1,r=0

#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
using pii=pair<int,int>;
using ll=long long;
constexpr int N=100010;
pii ST[N][22];
int lg[N];
int a[N],n,m;
int L[N],R[N],stk[N],top;
ll f[N],g[N];
int sz,pos[N];
void init(int n)
{
    lg[1]=0;
    for(int i=2;i<=n;i++) lg[i]=lg[i>>1]+1;
    for(int i=1;i<=n;i++) ST[i][0]={a[i],i};
    for(int k=1;k<=lg[n];k++)
        for(int i=1;i+(1<<k)-1<=n;i++)  
            ST[i][k]=min(ST[i][k-1],ST[i+(1<<k-1)][k-1]);
    
    top=0;
    for(int i=1;i<=n;i++)
    {
        while(top&&a[stk[top]]>a[i]) top--;
        L[i]=stk[top];
        stk[++top]=i;
    }
    stk[top=0]=n+1;
    for(int i=n;i>=1;i--)
    {
        while(top&&a[stk[top]]>a[i]) top--;
        R[i]=stk[top];
        stk[++top]=i;
    }
    
    for(int i=1;i<=n;i++) 
        f[i]=f[L[i]]+1ll*(i-L[i])*a[i];
    for(int i=n;i>=1;i--)
        g[i]=g[R[i]]+1ll*(R[i]-i)*a[i];
        
    sz=sqrt(n);
    for(int i=1;i<=n;i++) pos[i]=i/sz;
    
}
int query(int l,int r)// [l,r]最小值的位置
{
    int k=lg[r-l+1];
    return min(ST[l][k],ST[r-(1<<k)+1][k]).second;
}
struct node
{
    int l,r;
    int id;
    bool operator<(const node&o)const
    {
        return pos[l]<pos[o.l]||pos[l]==pos[o.l]&&r<o.r;
    }
}q[N];
ll calcR(int l,int r)
{
    int p=query(l,r);
    return 1ll*a[p]*(p-l+1)+f[r]-f[p];
}
ll calcL(int l,int r)
{
    int p=query(l,r);
    return 1ll*a[p]*(r-p+1)+g[l]-g[p];
}
ll ans[N],res;
int main()
{
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
    init(n);
    for(int i=1;i<=m;i++)
    {
        int l,r;
        cin>>l>>r;
        q[i]={l,r,i};
    }
    sort(q+1,q+1+m);
    int l=1,r=0;
    for(int i=1;i<=m;i++)
    {
        while(r<q[i].r) res+=calcR(l,++r);//首先考虑右端点
        while(r>q[i].r) res-=calcR(l,r--);
        while(l<q[i].l) res-=calcL(l++,r);
        while(l>q[i].l) res+=calcL(--l,r);
        ans[q[i].id]=res;
    }
    for(int i=1;i<=m;i++) cout<<ans[i]<<'\n';
    return 0;
}

注意我改了2小时,对比代码才改出来,无语了~~~
要加油哦~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值