Gym - 102302 K - Candies —— 主席树+后缀数组

This way

题意:

给你一个长度为n的数组,让你取一段子区间使得这个区间里值得和>=L,<=R
问你有多少个本质不同的区间。

题解:

难点在于本质不同。
我一开始想的是后缀自动机,但是发现忘了怎么做了,而且好像比较难处理。
转换成后缀数组时,发现height数组是知道排名为i和排名为i-1的后缀的最长公共前缀的,那么我们只需要按照sa数组的顺序便利,查询的区间是height[i]+sa[i]到n。
由于我找的后缀数组的板子,计算是从0到n-1的,所以做起来会比较麻烦。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6+10;
int wa[N],wb[N],wv[N],we[N],rk[N];
int cmp(int *r,int a,int b,int l){return r[a]==r[b]&&r[a+l]==r[b+l];}
void build_sa(int *r,int *sa,int n,int m){
    int i,j,p,*x=wa,*y=wb,*t;
    for(i=0;i<m;i++)we[i]=0;
    for(i=0;i<n;i++)we[x[i]=r[i]]++;
    for(i=1;i<m;i++)we[i]+=we[i-1];
    for(i=n-1;i>=0;i--)sa[--we[x[i]]]=i;
    for(j=1,p=1;p<n;j*=2,m=p){
        for(p=0,i=n-j;i<n;i++)y[p++]=i;
        for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j;
        for(i=0;i<n;i++)wv[i]=x[y[i]];
        for(i=0;i<m;i++)we[i]=0;
        for(i=0;i<n;i++)we[wv[i]]++;
        for(i=1;i<m;i++)we[i]+=we[i-1];
        for(i=n-1;i>=0;i--)sa[--we[wv[i]]]=y[i];
        for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
        x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
    }
}
int height[N];
void calheight(int *r,int *sa,int n){
    int i,j,k=0;
    for(i=1;i<=n;i++)rk[sa[i]]=i;
    for(i=0;i<n;height[rk[i++]]=k){
        for(k?k--:0,j=sa[rk[i]-1];r[i+k]==r[j+k];k++);
    }
}
int ls[N*40],rs[N*40],rt[N*40],tot;
int num[N*40];
int a[N],sa[N];
ll b[N],sum[N];
void update(int l,int r,int root,int last,int pos)
{
    ls[root]=ls[last];
    rs[root]=rs[last];
    num[root]=num[last]+1;
    if(l==r)
        return ;
    int mid=l+r>>1;
    if(mid>=pos)
        update(l,mid,ls[root]=++tot,ls[last],pos);
    else
        update(mid+1,r,rs[root]=++tot,rs[last],pos);
}
ll query(int l,int r,int root,int last,int ql,int qr)
{
    if(l>=ql&&r<=qr)
        return num[root]-num[last];
    int mid=l+r>>1;
    ll ans=0;
    if(mid>=ql)
        ans=query(l,mid,ls[root],ls[last],ql,qr);
    if(mid<qr)
        ans+=query(mid+1,r,rs[root],rs[last],ql,qr);
    return ans;
}
int main()
{
    int n;
    ll L,R;
    scanf("%d%lld%lld",&n,&L,&R);
    int all=0;
    for(int i=0;i<n;i++){
        scanf("%d",&a[i]);
        sum[i]=(i-1>=0?sum[i-1]:0)+a[i];
        b[++all]=sum[i];
        b[++all]=a[i];
    }
    sort(b+1,b+1+all);
    all=unique(b+1,b+1+all)-b-1;
    for(int i=0;i<n;i++)
        a[i]=lower_bound(b+1,b+1+all,(ll)a[i])-b;
    build_sa(a,sa,n+1,N-1);
    calheight(a,sa,n);
    for(int i=1;i<=n;i++){
        int x=lower_bound(b+1,b+1+all,sum[i-1])-b;
        update(1,all,rt[i]=++tot,rt[i-1],x);
    }
    ll ans=0;
    for(int i=1;i<=n;i++){
        int pos=sa[i]+1;
        ll s=pos>=2?sum[pos-2]:0;
        int l=lower_bound(b+1,b+1+all,s+L)-b;
        int r=upper_bound(b+1,b+1+all,s+R)-b-1;
        if(l<=r)
            ans+=query(1,all,rt[n],rt[pos+height[i]-1],l,r);
    }
    printf("%lld\n",ans);
    return 0;
}
/**/
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值