P5459 [BJOI2016]回转寿司 题解
P5459 [BJOI2016]回转寿司 题解
间隙
前置知识
-
前缀和,权值线段树,动态开点
如果您还不会权值线段树跟动态开点的话,推荐去看一下这个教程
大致题意
给一个序列,现从中取出一段连续子序列,使其子序列内数值总和\(a\)满足\(L\le a\le R\)
求总方案数。
分析
区间求和,很容易先联想到前缀和
不妨先设\(sum[i]\)为前\(i\)个数的前缀和
易得式子:
\(L \le sum[r] - sum[l-1] \le R\)
移项一下
$sum[r]-L \le sum[l-1] \le sum[r]-R $
这样原问题就转化为了在区间\([sum[r]-L,sum[r]-R]\)中有多少个\(sum[l-1]\)(\(l \in[1,r]\) )
每一个\(r\)也就相当于是查询区间\([sum[r]-L,sum[r]-R]\)中\(sum[l-1]\)的总和(\(l \in[1,r]\) )
可以使用权值线段树\(+\)动态开点来维护。
代码实现
从\(1\)~\(n\)枚举\(r\)的值,把每一个\(r\)当作一次"查询"
同时不要忘记在进行下一次"查询" 前把 \(l\) 的值 "更新"(指插入新的值)
具体的注释里有讲
#include<bits/stdc++.h>
using namespace std;
long long MAXN = 1e10;
const int N = 1e5+5;
long long sum[N];//前缀和
int n,l,r;
long long ans = 0;
int tot = 0;
struct st{
int l,r,sum;//左儿子,右儿子,总方案数
}tree[N<<10];
void pushup(int node){//上传操作
tree[node].sum = tree[tree[node].l].sum+tree[tree[node].r].sum;
}
void insert(int &node,long long x,long long l = -MAXN , long long r = MAXN){//更新 注意,l的初始值要设成负数,一开始在这里卡了好久\kk
if(!node) node = ++tot;//动态开点
if(l==r){//如果为根节点
tree[node].sum++;
return;
}
long long mid = (l + r)>>1;
if(x<=mid) update(tree[node].l,x,l,mid);
else update(tree[node].r,x,mid+1,r);
pushup(node);//更新父节点的值
}
long long query(int &node,long long x,long long y,long long l =-MAXN,long long r = MAXN){//查询操作
if(!node) node = ++tot;//动态开点
if(x<=l&&y>=r){//包含在查询范围内
return tree[node].sum;
}
long long res = 0;
long long mid = (l+r)>>1;
if(x<=mid) res+=query(tree[node].l,x,y,l,mid);
if(y>mid) res+=query(tree[node].r,x,y,mid+1,r);
return res;
}
int main(){
int root = 0;
scanf("%d%d%d",&n,&l,&r);
for(int i=1;i<=n;i++){
int a;
scanf("%d",&a);
sum[i] = sum[i-1] + a;//前缀和
}
insert(root,0);//不要忘记插入0,也就是说一个都不吃的情况
for(int i=1;i<=n;i++){
ans+=query(root,sum[i] - r,sum[i] - l);//加方案数
insert(root,sum[i]);//"更新"l的值
}
cout<<ans;
}