P5459 [BJOI2016]回轉壽司 題解
阿新 • • 發佈:2020-08-05
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; }