?你说你用什么过了平衡树

· · 算法·理论

ps:

本题不只有树状数组和【模板】普通平衡树。

思维难度为入门级。

前置芝士:树状数组。

我们知道它支持单点修改,前缀查询。懒标记搞不了,逆运算搞不了,复杂操作搞不了……所以树状数组有啥用?

答:逆天的码量和广泛的应用范围。码量和暴力接近,而很多题都只需要单点修改和前缀查询。

考虑维护差分序列。

:::success[0.47k]


#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=5e5+10;
ll c[N],n,m,o,x,y,k;
void upd(ll x,ll k)
{for(ll i=x;i<=n;i+=i&-i) c[i]+=k;}
int main(){
    scanf("%lld%lld",&n,&m);
    for(ll i=1;i<=n;i++)
    scanf("%lld",&x),upd(i,x-y),y=x;
    for(ll i=1;i<=m;i++){
        scanf"%lld%lld",&o,&x);
        if(o==1){
            scanf("%lld%lld",&y,&k);
            upd(x,k),upd(y+1,-k);
        }else{
            o=0;
            for(ll i=x;i;i-=i&-i) o+=c[i];
            printf("%lld\n",o);
        }
    }
    return 0;
}
:::

- [例 $2$(Green)](https://www.luogu.com.cn/problem/P3372):区间修改,区间查询

维护差分序列,查询前缀为 $r,r-1,...,1$ 系数。考虑维护原树状数组和系数 $n,n-1,...,n-r+1$ 的树状数组拼接。

:::success[0.55k]
```cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1e5+10;
ll a[N],b[N],n,m,o,x,y;
ll q(ll x){
    ll s=0;
    for(ll i=x;i;i-=i&-i)
    s+=b[i]-a[i]*(n-x);
    return s; 
}
void u(ll x,ll k){
    for(ll i=x;i<=n;i+=i&-i)
    a[i]+=k,b[i]+=(n-x+1)*k;
}
int main(){
    scanf("%lld%lld",&n,&m);
    for(ll i=1;i<=n;i++){
        scanf("%lld",&x);
        u(i,x-y),y=x;
    }
    while(m--){
        scanf("%lld",&o);
        scanf("%lld%lld",&x,&y);
        if(o==1){
            scanf("%lld",&o);
            u(x,o),u(y+1,-o);
        }else{
            ll t=q(y)-q(x-1);
            printf("%lld\n",t);
        }
    }
    return 0;
}
:::

- [例 $3$(Green)](https://www.luogu.com.cn/problem/P5076):维护 $10^4$ 大小,值域 $±10^9$ 的(不可重)集合,支持插入,排名和原数相互对应,前后继。

点击输入文本,直接暴力。不行用 `bitset` 卡常。

:::success[0.79k]
```cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1e4+10;
ll o[N],x[N],b[N],n,m,y;
bitset<N> a,A;
ll ct(ll x){
    bitset<N> B=A>>N-x-1;
    return (a&B).count();
}
ll nm(ll x){
    ll s=0;
    for(ll i=1;i<=x;i++){
        s+=a[i];
        if(s>=x) return i;
    }
}
int main(){
    scanf("%lld",&m);
    b[++n]=2147483647,b[++n]=-b[1];
    for(ll i=1;i<=m;i++){
        scanf("%lld%lld",&o[i],&x[i]);
        b[++n]=x[i];
    }
    sort(b+1,b+n+1),A.set();
    n=unique(b+1,b+n+1)-b-1;
    a[1]=a[n]=1; 
    for(ll i=1;i<=m;i++){
        if(o[i]==2) y=nm(x[i]+1);
        else{
            x[i]=upper_bound(b+1,b+n+1,x[i])-b-1;
            if(o[i]==5) a[x[i]]=1;
            else if(o[i]==1) y=ct(x[i]-1);
            else if(o[i]==3) y=nm(ct(x[i]-1));
            else if(o[i]==4) y=nm(ct(x[i])+1);
        }
        if(o[i]!=5&&o[i]!=1) y=b[y];
        if(o[i]<5) printf("%lld\n",y);
    }
    return 0;
}
:::

---

[引入:树状数组倍增/二分](https://www.luogu.com.cn/article/klof1kp5)

---

- [例 $4$(Blue)](https://www.luogu.com.cn/problem/P3369):例 $3$,大小 $10^5$,值域 $±10^7$。

前继:查询 $<x$ 的个数 $k$,然后找排名为 $k$ 的数。

后继:查询 $\le x$ 的个数 $k$,然后找排名为 $k+1$ 的数。

考虑权值树状数组,元素找排名为前缀查询,排名找元素为树状数组倍增/二分。

:::success[0.66k]
```cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll k=1e7+5,N=k<<1;
ll a[N],n,o,x;
ll ct(ll x){
    ll s=0;
    for(ll i=x+k;i;i-=i&-i)
    s+=a[i]; return s;
}
ll nm(ll x){
    ll s=0;
    for(ll i=24;i>=0;i--)
    if(s+(1<<i)<N&&a[s+(1<<i)]<x)
    s+=1<<i,x-=a[s]; return s+1-k;
}
int main(){
    scanf("%lld",&n);
    while(n--){
        scanf("%lld%lld",&o,&x);
        if(o<3)
        for(ll i=x+k;i<N;i+=i&-i)
        o==2?a[i]--:a[i]++;
        else if(o==3) x=ct(x-1)+1;
        else if(o==4) x=nm(x);
        else if(o==5) x=nm(ct(x-1));
        else x=nm(ct(x)+1);
        if(o>2) printf("%lld\n",x);
    }
    return 0;
}
:::

- [例 $5$(Blue)](https://www.luogu.com.cn/problem/P6136):例 $3$,大小 $10^6$,值域 $±10^9$,强制在线。

无法离散化,`unordered_map` 套树状数组暴力搞拿下 6s 佳绩。

于是乎,动态开点线段树维护。

点击输入文本。~~看来还需要空间卡到 128MB~~

:::success[1.02k]
```cpp
#include<bits/stdc++.h>
#define k (l+r>>1)
using namespace std;
typedef int ll;
const ll N=3e7+10,V=(1<<30)-1;
ll p[N],L[N],R[N];
ll n,m,o,x,y,K=1,z,c=1,F;
ll b(ll &x){return x?x:x=++c;}
void d(ll u,ll l,ll r){
    if(r==1) F++;
    if(l==r){p[u]+=K; return;}
    if(x<=k) d(b(L[u]),l,k);
    else d(b(R[u]),k+1,r);
    p[u]=p[L[u]]+p[R[u]];
}
ll q(ll u,ll l,ll r){
    if(!u) return 0; ll s=0;
    if(r<=x) return p[u];
    if(k<x) s=q(R[u],k+1,r);
    return s+q(L[u],l,k);
}
ll f(ll u,ll l,ll r){
    if(l==r) return l;
    if(x<=p[L[u]])
    return f(L[u],l,k);
    x-=p[L[u]];
    return f(R[u],k+1,r);
}
ll ct(ll t)
{x=t; return q(1,0,V);}
ll nm(ll t)
{x=t; return f(1,0,V);}
int main(){
    scanf("%d%d",&n,&m);
    while(n--)
    scanf("%d",&x),d(1,0,V);
    while(m--){
        scanf("%d%d",&o,&x),x^=y;
        if(o<3) K=o==2?-1:1,d(1,0,V);
        else if(o==3) y=ct(x-1)+1;
        else if(o==4) y=nm(x);
        else if(o==5) y=nm(ct(x-1));
        else y=nm(ct(x)+1);
        if(o>2) z^=y;
    }
    printf("%d",z);
    return 0;
}
:::

- [例 $6$(Blue)](https://www.luogu.com.cn/problem/P3919):数组操作,每次操作完视为一个版本,每次操作基于一个历史版本后操作。

考虑版本的更新构成了一颗树,离线,进入子树操作,退出子树撤销(记录原有的值)。

局限性:不可强制在线,必须可以撤销,不能同时处理多个版本。

:::success[0.56k]
```cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1e6+10;
vector<ll> e[N]; ll n,m,w;
ll p[N],c[N],a[N],s[N],o;
void dfs(ll i){
    ll t=a[p[i]];
    c[i]>1.5e9?s[i]=t:a[p[i]]=c[i];
    for(auto j:e[i]) dfs(j);
    a[p[i]]=t;
}
int main(){
    scanf("%lld%lld",&n,&m);
    for(ll i=1;i<=n;i++) 
    scanf("%lld",&a[i]); 
    for(ll i=1;i<=m;i++){
        scanf("%lld%lld",&w,&o);
        scanf("%lld",&p[i]);
        e[w].push_back(i);
        if(o==2) c[i]=2e9;
        else scanf("%lld",&c[i]);
    }
    dfs(0);
    for(ll i=1;i<=m;i++)
    if(c[i]>1.5e9)
    printf("%lld\n",s[i]);
    return 0;
}
:::

- [例 $7$(Purple)](https://www.luogu.com.cn/problem/P3402):版本并查集。

建离线版本树(我起的名字,应该叫啥,问),按秩合并可撤销并查集即可。

:::success[1.01k]
```cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1e5+10,M=N<<1;
vector<ll> a[M]; ll n,m,k;
ll d[N],s[N],o[M],x[M],y[M];
ll f(ll x){return d[x]?f(d[x]):x;}
void dfs(ll i){
    if(o[i]==1){
        ll X=f(x[i]),Y=f(y[i]);
        if(X==Y) x[i]=y[i]=0;
        else{
            if(s[X]>s[Y]) swap(X,Y);
            x[i]=X,y[i]=Y;
            s[Y]+=s[X],d[X]=Y;
        }
    }else if(o[i]==3)
    x[i]=f(x[i])==f(y[i]);
    for(auto j:a[i]) dfs(j);
    if(o[i]==1)
    s[y[i]]-=s[x[i]],d[x[i]]=0;
}
int main(){
    scanf("%lld%lld",&n,&m);
    for(ll i=1;i<=m;i++){
        scanf("%lld%lld",&o[i],&x[i]);
        o[i]==2?k=x[i]:scanf("%lld",&y[i]);
        a[k].push_back(i),k=i;
    }
    for(ll i=1;i<=n;i++) s[i]=1; dfs(0);
    for(ll i=1;i<=m;i++) if(o[i]==3)
    printf("%lld\n",x[i]);
    return 0;
}
:::

- [例 $8$(Purple)](https://www.luogu.com.cn/problem/P3835):例 $4$ 版本板,$5\times10^5$ 个元素,值域 $±10^9$。

离散化值域,建离线版本树,同例 $5$。

:::success[1.01k]
```cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=5e5+10;
vector<ll> a[N];
ll c[N],o[N],v[N],b[N],n=2,m,x;
void u(ll x,ll k)
{for(ll i=x;i<=n;i+=i&-i) c[i]+=k;}
ll t(ll x){
    ll s=0;
    for(ll i=x;i;i-=i&-i) s+=c[i];
    return s;
}
ll w(ll x){
    ll s=0;
    for(ll i=1<<19;i;i>>=1)
    if(s+i<=n&&c[s+i]<x) x-=c[s+=i];
    return s+1;
}
void dfs(ll i){
    ll y=0;
    if(o[i]==4) v[i]=w(v[i]+1);
    else{
        v[i]=lower_bound(b+1,b+n+1,v[i])-b;
        if(o[i]==1) u(v[i],1);
        if(o[i]==2&&t(v[i])-t(v[i]-1))
        y=1,u(v[i],-1);
        if(o[i]==3) v[i]=t(v[i]-1);
        if(o[i]==5) v[i]=w(t(v[i]-1));
        if(o[i]==6) v[i]=w(t(v[i])+1);
    }
    if(o[i]>3) v[i]=b[v[i]]; 
    for(auto j:a[i]) dfs(j);
    if(o[i]==1) u(v[i],-1);
    if(o[i]==2) u(v[i],y);
}
int main(){
    scanf("%lld",&m);
    b[1]=-1ll<<31,b[2]=-b[1];
    for(ll i=1;i<=m;i++){
        scanf("%lld%lld",&x,&o[i]);
        scanf("%lld",&v[i]);
        a[x].push_back(i),b[++n]=v[i];
    }
    sort(b+1,b+n+1);
    n=unique(b+1,b+n+1)-b-1;
    u(1,1),u(n,1),dfs(0);
    for(ll i=1;i<=m;i++) if(o[i]>2) 
    printf("%lld\n",v[i]);
    return 0;
}
:::

- [例 $9$(Purple)](https://www.luogu.com.cn/problem/P3380):给定序列,支持序列单点修改,查询区间同例 $4$ 操作。

树状数组套树状数组,`unordered_map` 维护,时间复杂度 $O(n\log^3n)$。需要访问 `unordered_map` 时判断 `count()` 卡常。

可以树状数组套动态开点线段树做到标算。

:::success[1.06k]
```cpp
#include<bits/stdc++.h>
using namespace std;
typedef int ll;
const ll N=5e4+10,K=1e8+10;
const ll M=(-1ll<<31)+1;
unordered_map<ll,ll> b[N];
ll h[N],n,m,o,l,r,k,y;
ll a(ll x,ll y)
{return b[x].count(y)?b[x][y]:0;}
void ud(ll x,ll y,ll k){
    for(ll i=x;i<=n;i+=i&-i)
    for(ll j=y+1;j<=K;j+=j&-j)
    b[i][j]+=k;
}
ll ct(ll l,ll r,ll k){
    ll s=0;
    for(ll j=k+1;j;j-=j&-j){
        for(ll i=r;i;i-=i&-i)
        s+=a(i,j);
        for(ll i=l-1;i;i-=i&-i)
        s-=a(i,j);
    }
    return s;
}
ll nm(ll l,ll r,ll k){
    if(!k) return M; ll s=0;
    for(ll j=1<<26;j;j>>=1){
        ll t=s+j,f=0;
        if(t>1e8) continue;
        for(ll i=r;i;i-=i&-i)
        f+=a(i,t);
        for(ll i=l-1;i;i-=i&-i)
        f-=a(i,t);
        if(f<k) k-=f,s=t;
    }
    return k>1e8+5?-M:s;
}
int main(){
    scanf("%d%d",&n,&m);
    for(ll i=1;i<=n;i++)
    scanf("%d",&h[i]),ud(i,h[i],1); 
    while(m--){
        scanf("%d%d",&o,&l);
        if(o!=3) scanf("%d",&r);
        scanf("%d",&k);
        if(o==1) y=ct(l,r,k-1)+1;
        if(o==2) y=nm(l,r,k);
        if(o==3) ud(l,h[l],-1),
        ud(l,h[l]=k,1);
        if(o==4)
        y=nm(l,r,ct(l,r,k-1));
        if(o==5)
        y=nm(l,r,ct(l,r,k)+1);
        if(o!=3) printf("%d\n",y);
    }
    return 0;
}
:::
:::success[1.45k $O(n\log^2n)$]
实测快 $3$ 倍。
```cpp
#include<bits/stdc++.h>
#define f (l+r>>1)
using namespace std;
typedef int ll;
const ll N=5e4+10,M=3e7+10;
const ll V=1e8+1,K=(-1<<31)+1;
struct D{ll u,k;}; ll x,y,c,l,r;
ll p[M],L[M],R[M],b[N],n,m,o,k,z;
ll g(ll &x){return x?x:x=++c;}
void d(ll u,ll l,ll r){
    if(l==r){p[u]+=k; return;}
    if(x<=f) d(g(L[u]),l,f);
    else d(g(R[u]),f+1,r);
    p[u]=p[L[u]]+p[R[u]];
}
ll q(ll u,ll l,ll r){
    if(!u) return 0;
    if(r<=x) return p[u];
    ll s=q(L[u],l,f);
    if(f<x) s+=q(R[u],f+1,r); 
    return s;
}
ll h(vector<D> u,ll l,ll r){
    if(l==r) return l==V?-K:l;
    vector<D> _l,_r; ll t=0;
    for(auto v:u){
        _l.push_back({L[v.u],v.k});
        _r.push_back({R[v.u],v.k});
        t+=p[L[v.u]]*v.k;
    }
    if(k<=t) return h(_l,l,f);
    k-=t; return h(_r,f+1,r);
}
void ud(ll a,ll b,ll c){
    for(ll i=a;i<=n;i+=i&-i)
    x=b,k=c,d(i,0,V);
}
ll ct(ll l,ll r,ll c){
    ll s=0; x=c;
    for(ll i=r;i;i-=i&-i)
    s+=q(i,0,V);
    for(ll i=l-1;i;i-=i&-i)
    s-=q(i,0,V);
    return s;
}
ll nm(ll l,ll r,ll c){
    if(!c) return K;
    k=c; vector<D> u;
    for(ll i=r;i;i-=i&-i)
    u.push_back({i,1});
    for(ll i=l-1;i;i-=i&-i)
    u.push_back({i,-1});
    return h(u,0,V);
}
int main(){
    scanf("%d%d",&n,&m),c=n;//`c
    for(ll i=1;i<=n;i++)
    scanf("%d",&b[i]),ud(i,b[i],1);
    while(m--){
        scanf("%d%d",&o,&l);
        if(o!=3) scanf("%d",&r);
        scanf("%d",&z);
        if(o==1) z=ct(l,r,z-1)+1;
        if(o==2) z=nm(l,r,z);
        if(o==3) ud(l,b[l],-1),
        ud(l,b[l]=z,1);
        if(o==4)
        z=nm(l,r,ct(l,r,z-1));
        if(o==5)
        z=nm(l,r,ct(l,r,z)+1);
        if(o!=3) printf("%d\n",z);
    }
    return 0;
} 
:::

---

总结:

~~一本正经地扯神秘做法。~~

鉴于局限性,请大家认真学习正常数据结构的同时,善于使用以上或 STL 简化代码,~~别上来就各种数据结构乱上。~~