值域线段树and动态开线段树

值域线段树每一个节点代表一个值,其他没什么区别

动态开树就是节省了没有用到节点,其中重要一点的是不需要节点是连续的(即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
", ans);
    return 0;
}
View Code
要么优秀要么生锈
原文地址:https://www.cnblogs.com/Superwalker/p/7827942.html