BZOJ 4540 HNOI2016 序列

本文介绍了一种使用莫队算法优化区间查询的方法,针对BZOJ上的一道题,通过预处理和单调栈优化实现O(1)的区间转移,解决了多组询问的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Problem

BZOJ

Solution

刚开始想的时候,觉得要预处理出a[i]做贡献的区域,然后用线段树做。但是这无法解决多组询问。
然后想莫队,发现只会logn的转移。。
然后这就比较尴尬了……

那么我们就重点缩一下如何O(1)转移吧
先讨论r++的情况。
很明显,我们会新增r-l+2个区间,更新答案就需要计算这些区间的贡献
首先我们可以发现在这些区间内,做贡献的值是一个递增的序列。
就比如4 1 3再新增一个2
那么对于新增的四个区间[4,1,3,2],[1,3,2],[3,2][2],做贡献的值分别为1,1,2,2
而且在贡献的值改变时,就是遇到比自己更小的值时。那么我们就可以将这个贡献进行前缀和的预处理,预处理可以用单调栈优化。
但是需要注意的是最左边,也就是最小的数它做贡献的区间是有可能不完整的,需要特别处理一下,设它的位置为pos,那么它的贡献为a[pos]*(pos-l+1)。查询pos用st表解决。

对于l–的情况,与r++所类似,把前缀和改成后缀和即可。

对于另外两个操作是类似的处理方式。

Code

#include <algorithm>
#include <cstdio>
#include <cmath>
using namespace std;
typedef long long ll;
const int maxn=100010;
struct query{
    int l,r,pos,id;
    bool operator < (const query &x)const
    {
        if(pos==x.pos) return r<x.r;
        return pos<x.pos;
    }
}q[maxn];
int n,m,sqr,top,l,r,a[maxn],lg[maxn],st[maxn][17],stk[maxn],pre[maxn],nxt[maxn];
ll tmp,ans[maxn],sum[maxn],suf[maxn];
template <typename Tp> inline void read(Tp &x)
{
    x=0;int f=0;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=1,ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    if(f) x=-x;
}
void input()
{
    read(n);read(m);
    sqr=(int)sqrt(n);
    for(int i=1;i<=n;i++){read(a[i]);st[i][0]=i;}
    for(int i=1;i<=m;i++)
    {
        read(q[i].l);read(q[i].r);
        q[i].pos=(q[i].l-1)/sqr+1;q[i].id=i;
    }
    sort(q+1,q+m+1);
}
int query(int l,int r)
{
    int k=lg[r-l+1],pos=r-(1<<k)+1;
    return a[st[l][k]]<a[st[pos][k]]?st[l][k]:st[pos][k];
}
void update(int op)
{
    int pos=query(l,r);
    switch(op)
    {
        case 1:tmp+=(ll)(pos-l+1)*a[pos]+sum[r]-sum[pos];break;
        case 2:tmp-=(ll)(pos-l+1)*a[pos]+sum[r]-sum[pos];break;
        case 3:tmp-=(ll)(r-pos+1)*a[pos]+suf[l]-suf[pos];break;
        case 4:tmp+=(ll)(r-pos+1)*a[pos]+suf[l]-suf[pos];break;
    }
}
void init()
{
    lg[0]=-1;
    for(int i=1;i<=n;i++) lg[i]=lg[i>>1]+1;
    for(int i=1;i<=16;i++)
      for(int j=1;j<=n;j++)
      {
        int r=j+(1<<(i-1));
        if(r<=n) st[j][i]=a[st[j][i-1]]<a[st[r][i-1]]?st[j][i-1]:st[r][i-1];
      }
    for(int i=1;i<=n;i++)
    {
        while(top&&a[i]<a[stk[top]]) nxt[stk[top--]]=i;
        pre[i]=stk[top];stk[++top]=i;
    }
    while(top) nxt[stk[top--]]=n+1;
    for(int i=1;i<=n;i++) sum[i]=sum[pre[i]]+(ll)(i-pre[i])*a[i];
    for(int i=n;i>=1;i--) suf[i]=suf[nxt[i]]+(ll)(nxt[i]-i)*a[i];
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    #endif
    input();
    init();
    tmp=a[1];l=r=1;
    for(int i=1;i<=m;i++)
    {
        while(r<q[i].r) r++,update(1);
        while(l>q[i].l) l--,update(4);
        while(r>q[i].r) update(2),r--;
        while(l<q[i].l) update(3),l++;
        ans[q[i].id]=tmp;
    }
    for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值