1. 程式人生 > 實用技巧 >P5459 [BJOI2016]回轉壽司 題解

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;
}