P5459 [BJOI2016]回转寿司 题解

P5459 [BJOI2016]回转寿司 题解

间隙

前置知识

  • 前缀和,权值线段树,动态开点

如果您还不会权值线段树跟动态开点的话,推荐去看一下这个教程

大致题意

给一个序列,现从中取出一段连续子序列,使其子序列内数值总和(a)满足(Lle ale 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;
} 

原文地址:https://www.cnblogs.com/xcxc82/p/13438974.html