值域線段樹每一個節點代表一個值,其他沒什么區別
動態開樹就是節省了沒有用到節點,其中重要一點的是不需要節點是連續的(即id值是任意的,只要可以找到即可)
例題
Bzoj 4627 回轉壽司
題意
給n個數問區間和在L<=sum【r】-sum【l-1】<=R區間有多少個
(N≤100000,|Ai|≤100000,0≤L, R≤10^9)
分析
由於區間和不單調,所以不能二分求取,分析可得推出公式 sum(r)-R<=sum(l-1)<=sum(r)-L ([l,r]是一個滿足條件的區間)
直接計算當前前綴和之前滿足條件的前綴和個數,累加起來則為答案
故考慮利用線段樹,每次讓對應的前綴和加1,然后統計滿足條件的個數,但是這個數最大可以達到1e10,不可能開這么大的空間,
但N最大10w,和的種類也就10w,故考慮動態開線段樹,每次只開根到這個節點上路徑上經過的點,這樣最多開log(N)*N即可,
這樣的話每次必須保存當前節點的左右兒子的id才可以准確訪問左右兒子,其他和線段樹差不多
trick:注意開始要處理sum【0】,這樣才可以保證當l==r時滿足條件也能被統計上

#include<bits/stdc++.h> #define ll long long using namespace std; const int maxn = 1e7 + 10; ll tree[maxn], lson[maxn], rson[maxn]; ll tot; ll a[100005]; ll sum[100005]; const ll inf = 1e10; int n; ll root=1; int newnode() { ++tot; tree[tot] = lson[tot] = rson[tot] = 0; return tot; } void update(ll &x, ll l, ll r, ll val) { if(!x) x=newnode(); tree[x]++; if(l == r) return; ll mid = (l+r)>>1; if(val<=mid ) update(lson[x], l, mid, val); else update(rson[x], mid+1, r, val); } ll query(ll x, ll l, ll r, ll ql, ll qr) { if(ql<=l && qr>=r) return tree[x]; ll mid = (l + r)>> 1; ll ans = 0; if(ql<=mid && lson[x]) ans +=query(lson[x], l , mid, ql, qr); if(qr>mid && rson[x]) ans+=query(rson[x], mid+1, r, ql, qr); return ans; } int main() { ll n, l ,r; scanf("%lld%lld%lld", &n, &l, &r); for(int i = 1; i <= n; i++) { scanf("%lld", &a[i]); sum[i]=sum[i-1]+a[i]; } ll ans=0; update(root, -inf, inf, 0); for(int i = 1; i<=n;i++) { ans+=query(root, -inf, inf, sum[i]-r, sum[i]-l); update(root,-inf,inf,sum[i]); } printf("%lld\n", ans); return 0; }