重逢可持久化线段树

与可持久化线段树重逢在了Codeforces Educational Round 80的E题,还好及时意识到了是可持久化线段树的经典操作——统计区间内不同的数字个数。

于是乎,赛后又重新复习了一下可持久化线段树,

可持久化意味着需要建立多个版本,为了节省空间,利用增量修改的想法,我们只去记录那些发生变化的部分(因此要求整个数据结构的结构不能发生变化,针对那些带旋转的平衡树),

往往要追求的目标是发生修改的部分是从根到叶子的一条链,树高是O(logN)的,因此只会新增O(logN)个节点,最后的节点总数是O(N+MlogN)。

其实这就限制了绝大部分修改都只能是单点修改。

而建立完可持久化线段树之后呢,无论是修改还是查询操作,我们在确定完在哪个版本之后,都只需要当成是在一棵线段树上考虑即可。

我觉得思考一个问题是否可以用可持久化线段树来做,关键还是思考线段树维护的是什么信息,从而能否把修改操作变成单点修改,或者查询的操作可以如愿完成。

然后有些查询操作需要满足可减性,比如最常见的区间第k大。

然后有时候会把问题搬到树上,一般是再多考虑LCA或者LCA的父亲的情况,具体的应用会非常灵活,需要具体问题具体分析。

静态区间第K大——我们建立权值线段树,每个节点代表的是权值的出现次数,非叶子节点记录的是区间内的权值的出现次数,然后从左到右往上一个版本插入新的数形成新的版本。查找第K大的时候从上到下直接根据数值关系找就行了。

不强制在线的话还可以用离线+树状数组写,常数会小很多。(关于离线处理的数据结构题,过几天会总结一下之前遇到过的几道,待更)

区间内不同的数字个数——这个我觉得非常巧妙,因为查询的是一个区间,当右端点固定的时候,肯定是越往右的数字越容易出现在区间中,因为我们把那些到目前为止最后一次出现该数字的位置设为1,其余都为0,然后在区间内查找[l,r]范围内的区间和即可。修改都是单点修改。

区间内前K小的值的总和——其实只要在第K大的基础上再维护个权值区间对应的总和就可以了。

此外,有些问题可能用二分的思想会降低思维难度,然后里面再套个可持久化线段树,但是这样复杂度有时候会多一个log,有时候可以直接在主席树上一路往下找,省掉那个log。

最后,贴一下这次E题我的做法吧,观察到一个数字出现过就变成了第一,然后就不可能往前,要么不动,要么往后移,直到下次出现。那么相邻两次出现的间隔中出现的不同的数字个数就是一个极大值,去更新最大值即可,最小值比较朴素,判断是否出现过即可。为了避免边界条件,把原数组扩充了一下,一定程度导致空间有点吃紧。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=600005;
struct node{
    int l,r,sum;
}T[maxn*32];
int n,m,a[maxn],cnt,root[maxn],pos[maxn];
vector<int> v[maxn+5];
int mn[maxn+5],mx[maxn+5];
void update(int &now,int pre,int val,int l,int r,int pos){
    T[++cnt]=T[pre],T[cnt].sum+=val,now=cnt;
    if(l==r) return;
    int mid=l+r>>1;
    if(pos<=mid) update(T[now].l,T[pre].l,val,l,mid,pos);
    else update(T[now].r,T[pre].r,val,mid+1,r,pos);
}
int query(int L,int R,int l,int r,int rt){
    if(L<=l&&R>=r) return T[rt].sum;
    int ans=0,mid=l+r>>1;
    if(L<=mid) ans+=query(L,R,l,mid,T[rt].l);
    if(R>mid) ans+=query(L,R,mid+1,r,T[rt].r);
    return ans;
}
int main(){
    scanf("%d",&n);
    scanf("%d",&m);
    for (int i=1;i<=n;i++) a[i]=n-i+1;
    for (int i=n+1;i<=n+m;i++) scanf("%d",&a[i]);
    for (int i=1;i<=n+m;i++) v[a[i]].push_back(i);
    for (int i=1;i<=n+m;i++){
        if(pos[a[i]]){
            update(root[i],root[i-1],-1,1,n+m,pos[a[i]]);
            update(root[i],root[i],1,1,n+m,i);
        }else update(root[i],root[i-1],1,1,n+m,i);
        pos[a[i]]=i;
    }
    for (int i=1;i<=n;i++)
    if (v[i][v[i].size()-1]>n) {
        mn[i]=1;
    }
    else mn[i]=i;
    for (int i=1;i<=n;i++) {
        for (int j=0;j<v[i].size()-1;j++) {
            int lef=v[i][j],rig=v[i][j+1];
            mx[i]=max(mx[i],query(lef,rig,1,n+m,root[rig]));
        }
        mx[i]=max(mx[i],query(v[i][v[i].size()-1],n+m,1,n+m,root[n+m]));
    }
    for (int i=1;i<=n;i++) printf("%d %d
",mn[i],mx[i]);
    return 0;
}
原文地址:https://www.cnblogs.com/xyw5vplus1/p/12203484.html