P5459 [BJOI2016]回转寿司 (权值线段树+动态开点)

题目链接

题意:给定一个序列,给定区间[L,R] ,问序列有多少子串的和属于这个区间。

分析:

我们先处理得到前缀和数组,pre[] ,子串[l,r]的和即:pre[r]-pre[l-1]
问题即:对于L<=pre[r]-pre[l-1]<=R (1<=l<=r<=n ),有多少对l r满足它

我们转化一下变成:pre[r]-R<=pre[l-1]<=pre[r]-L (1<=l<=r<=n )

到这里可以想到一种思路:
我们枚举r同时把pre数组元素存进桶里,
对于每个r都能得到区间[pre[r]-R , pre[r]-L ] ,我们查桶里这个区间里面有多少个pre数组元素即可。 但是如果暴力查询的话明显时间和空间上都不行。

想到可以用权值线段树来维护桶:
我们枚举r查询当前线段树中桶的值,查询结束后把pre[r]插入线段树。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 1e5+7;
const ll INF = 1e10;
int tree[maxn*150],lc[maxn*150],rc[maxn*150];//动态开点
int root=1,cnt=1;
ll sum[maxn],L,R;
int n;
inline void pushup(int &rt)
{
    tree[rt]=tree[lc[rt]]+tree[rc[rt]];
    return ;
}
inline void updata(int &rt,ll l,ll r,ll x)
{
    if(!rt) rt=++cnt;
    if(l == r)
    {
        tree[rt]++;
        return ;
    }  
    ll mid=(l+r)>>1;
    if(x<=mid) updata(lc[rt],l,mid,x);
    else updata(rc[rt],mid+1,r,x);
    pushup(rt);
    return ;
}
inline int query(int rt,ll l,ll r,ll vl,ll vr)//区间[vl,vr]数出现的个数
{
    if(r<vl || l>vr || !rt) return 0;
    if(vl<=l && r<=vr) return tree[rt];
    ll mid=(l+r)>>1;
    return query(lc[rt],l,mid,vl,vr)+query(rc[rt],mid+1,r,vl,vr);
}
int main()
{
    scanf("%d %lld %lld",&n,&L,&R);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",sum+i);
        sum[i]+=sum[i-1];
    }
    updata(root,-INF,INF,sum[0]);//l==1的情况,[1,r]的子串和
    ll ans=0;
    for(int i=1;i<=n;i++)
    {
        ans+=query(root,-INF,INF,sum[i]-R,sum[i]-L);
        //l-1在[0,r)内
        updata(root,-INF,INF,sum[i]);
    }
    printf("%lld\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值