zoukankan      html  css  js  c++  java
  • 【线段树】主席树学习笔记

    摘:

    主席树思想是每个位置都维护一个线段树,线段树的节点是值的范围,然后第i个线段树中某个区间[x, y]维护的是,1-i中数字在[x, y]范围内的个数。这里利用到了前缀和的思想。

    (想学好就得老老实实做笔记,不要相信自己的记忆力。

    个人笔记:

    关于(静态的)主席树(动态还没学....... ):

    1,主席树是高级的权值线段树。

      权值线段树区别于普通线段树是:权值线段树是以权值为区间建立起来的线段树,(l,r)是权值区间。其主要作用是记录权值区间里的数出现的总个数,或 单一权值出现的个数。有利于查询<所有权值中>出现的第k大(小)值。

    2,主席树和权值线段树关联的地方是,其子结构(主要结构)是权值线段树。

      设全局要处理的区间为[1,n],则n个连续区间[1,x](1≤x≤n)是n棵权值线段树。

      假设真实建立独立的这n棵权值线段树,其消耗空间约为n^2.....

      主席树就是,尽量共用节点节省空间建立这n棵线段树,每棵线段树的建立是在上一棵线段树的基础上共用节点建立起来的。

      主席树模板题是求区间第k大,设区间为(l,r),则所求就是 在满足树T[r]与树T[l-1]权值区间 权值数量差为k的条件下 不断缩小的到的 l(r)

    3,据说:每棵线段树不一定是由上一时刻转移来的,要因题而异。

    4,可持久化线段树的三种建树方法:

      (1)、对序列建树,常见方法,每个点一个时刻的树。

      (2)、树上主席树,树上每个点一棵线段树,每个点的线段树从父节点修改而来。

      (3)、对深度建立主席树,每个深度建立一棵主席树,将该深度的所有点都加进来,当树上问题对深度有限制时用这个。

      

    5,关于树上主席树,学习笔记:参考题目洛谷P2633 Count on a tree

      _1,树上主席树的做法通常还涉及两个知识点:

       (1) 树上差分。(基于:每个点的线段树是从父节点修改而来,才有效)

        目前不懂,只知道对于区间[l,r],它的直接路径的点数是num[ T[l] ]+num[ T[r] ]-num[ T[lca] ]-num[ T[fa_lca] ];

                       直接路径的权值和是sum[ T[l] ]+sum[ T[r] ]-sum[ T[lca] ]-sum[ T[fa_lca] ];

       (2)  lca是点l和r的最近公共祖先,fa_lca是点lca的直接父亲。num,sum,是主席树统计出来的值。

         关于最近公共祖先,我目前的做法是直接套用树链剖分的top[]数组来求,更好的做法似乎是用倍增数组,还不会,待学。

      _2,树上主席树的建立方法是通过dfs序来建,但并非真的是dfs序,应该说是,每个节点的线段树是由它的父节点修改而来,树上差分是基于这种做法才有效。

    洛谷P2633 Count on a tree

    #include<bits/stdc++.h>
    #define debug printf("!");
    using namespace std;
    typedef long long ll;
    const int inf=0x3f3f3f3f;
    const int maxn=5e5+50;
    const int mod=998244353;
    
    int n,a[maxn],h[maxn],nn;
    vector<int>p[maxn];
    
    int val(int i)
    {
        return lower_bound(h+1,h+1+nn,a[i])-h;
    }
    
    
    int T[maxn],L[maxn*20],R[maxn*20],sum[maxn*20],tot;
    
    void build(int& rt,int l,int r)
    {
        rt=++tot;
        sum[rt]=0;
        if(l==r)return;
        int mid=(l+r)>>1;
        build(L[rt],l,mid);
        build(R[rt],mid+1,r);
    }
    void update(int &rt,int l,int r,int pre,int x)
    {
        rt=++tot;
        L[rt]=L[pre];
        R[rt]=R[pre];
        sum[rt]=sum[pre]+1;
        if(l==r)return;
        int mid=(l+r)>>1;
        if(x<=mid)update(L[rt],l,mid,L[pre],x);
        else update(R[rt],mid+1,r,R[pre],x);
    }
    int query(int x,int y,int lca,int falca,int l,int r,int k)
    {
        if(k>sum[x]+sum[y]-sum[lca]-sum[falca])return 0;
        if(l==r)return l;
        int mid=(l+r)>>1;
        int s=sum[L[x]]+sum[L[y]]-sum[L[lca]]-sum[L[falca]];
        if(k<=s)return query(L[x],L[y],L[lca],L[falca],l,mid,k);
        else return query(R[x],R[y],R[lca],R[falca],mid+1,r,k-s);
    }
    
    int id[maxn],rid[maxn],fa[maxn],son[maxn],top[maxn],siz[maxn],dep[maxn],cnt;
    void dfs1(int u,int f,int d)
    {
        siz[u]=1;dep[u]=d;
        id[u]=++cnt;rid[cnt]=u;fa[u]=f;
        update(T[u],1,nn,T[f],val(u));
        for(int i=0;i<p[u].size();i++)
        {
            if(p[u][i]==f)continue;
            dfs1(p[u][i],u,d+1);
            siz[u]+=siz[p[u][i]];
            if(siz[p[u][i]]>siz[son[u]])son[u]=p[u][i];
        }
    }
    void dfs2(int u,int t)
    {
        top[u]=t;
        if(!son[u])return;
        dfs2(son[u],t);
        for(int i=0;i<p[u].size();i++)
        {
            if(p[u][i]!=son[u]&&p[u][i]!=fa[u])dfs2(p[u][i],p[u][i]);
        }
        
    }
    int callca(int x,int y)
    {
        while(top[x]!=top[y])
        {
            if(dep[top[x]]<dep[top[y]])swap(x,y);
            x=fa[top[x]];
        }
        if(dep[x]>dep[y])return y;
        else return x;
    }
    
    int main()
    {
        int m,i,j,k,u,v,ans=0,falca,lca;
        scanf("%d%d",&n,&m);
        for(i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            h[i]=a[i];
        }
        sort(h+1,h+1+n);
        nn=unique(h+1,h+1+n)-h-1;
        for(i=1;i<n;i++)
        {
            scanf("%d%d",&u,&v);
            p[u].push_back(v);
            p[v].push_back(u);
        }
        build(T[0],1,nn);
        siz[0]=0;sum[0]=0;
        dfs1(1,0,1);
        dfs2(1,1);
        while(m--)
        {
            scanf("%d%d%d",&u,&v,&k);
            u^=ans;
            lca=callca(u,v);
            falca=fa[lca];
            ans=query(T[u],T[v],T[lca],T[falca],1,nn,k);
            ans=h[ans];
            printf("%d
    ",ans);
        }
    }
    View Code

    2019-09-29 22:38:36



     先照着这个博客hdu2665

    静态主席树

    感觉hdu2665可以用划分树做 但是划分树之前看过还没好好好好学

    等这次主席树学得差不多 再回去看看划分树,试试能不能用划分树做

    静态主席树第一题 hdu2665  or  洛谷P3834 求静态第k小

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1e5+50;
    
    //求静态区间第k小
    
    //sum[] 需要根据题意来修改 不是主席树自带 
    
    int a[maxn],h[maxn];
    //a是原序列 h是离散后的序列
    
    int T[maxn],L[maxn*20],R[maxn*20],sum[maxn*20];
    /*
    T[i] 是第i颗线段树的根节点编号
    L[i] 是节点i的左节点编号 
    R[i] 是节点i的右节点编号 
    sum[i] 是节点i对应区间 数 的 个数
    */
    int tot;
    //rt tot是节点编号
    
    //建空树 build(T[0],1,n);
    // T[0]=1; 
    void build(int& rt,int l,int r)
    {
        rt=++tot;
        sum[rt]=0;
        if(l==r)return;
        int mid=(l+r)>>1;
        build(L[rt],l,mid);
        build(R[rt],mid+1,r);
    }
    
    //更新rt(T[i]) pre=T[i-1]
    void update(int &rt,int l,int r,int pre,int x)
    {
        rt=++tot;
        L[rt]=L[pre];
        R[rt]=R[pre];
        sum[rt]=sum[pre]+1;//添加一个数 sum值比前驱线段树+1 
        if(l==r)return ;
        int mid=(l+r)>>1;
        //节点复用体现在这个if else语句
        //只新建包含x的区间节点 
        if(x<=mid)update(L[rt],l,mid,L[pre],x);
        else update(R[rt],mid+1,r,R[pre],x); 
    }
    
    int query(int s,int e,int l,int r,int k)
    {
        if(l==r)return l;
        int mid=(l+r)>>1;
        int res=sum[L[e]]-sum[L[s]];
        if(k<=res)return query(L[s],L[e],l,mid,k);
        else return query(R[s],R[e],mid+1,r,k-res); 
    }
    
    int main()
    {
        int tt;
        scanf("%d",&tt);
        while(tt--)
        {
            int n,m,i,s,t,k,nn,ans;
            scanf("%d%d",&n,&m);
            for(i=1;i<=n;i++)
            {
                scanf("%d",&a[i]);h[i]=a[i];
            }
            sort(h+1,h+n+1);
            nn=unique(h+1,h+1+n)-h-1;
            tot=0;
            build(T[0],1,nn);
            for(i=1;i<=n;i++)
                update(T[i],1,nn,T[i-1],lower_bound(h+1,h+1+nn,a[i])-h);
                //因为是[1,nn] 不是[0,nn] 所以是-h 不是-(h+1)
            while(m--)
            {
                scanf("%d%d%d",&s,&t,&k);
                ans=query(T[s-1],T[t],1,nn,k);
                printf("%d
    ",h[ans]);
            }
        }
    }
    View Code

    2019-09-11 09:51:44


     洛谷P3919

    (可持久化线段树和主席树一样吗?... 

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1e6+50;
    
    
    int a[maxn];
    
    int T[maxn],L[maxn*25],R[maxn*25],val[maxn*25];
    /*
    T[i] 是第i颗线段树的根节点编号
    L[i] 是节点i的左节点编号 
    R[i] 是节点i的右节点编号 
    */
    int tot;
    //rt tot是节点编号
    
    
    void build(int&rt,int l,int r)
    {
        rt=++tot;
        if(l==r)
        {
            val[rt]=a[l];return;
        }
        int mid=(l+r)>>1;
        build(L[rt],l,mid);
        build(R[rt],mid+1,r);
    }
    
    
    int update(int&rt,int l,int r,int pre,int id,int x,int op)
    {
        rt=++tot;
        if(l==r)
        {
            if(op==1)val[rt]=x;
            else val[rt]=val[pre];
            return val[rt];
        }
        L[rt]=L[pre];
        R[rt]=R[pre];
        int mid=(l+r)>>1;
        if(id<=mid)return update(L[rt],l,mid,L[pre],id,x,op);
        else return update(R[rt],mid+1,r,R[pre],id,x,op);
    }
    /*
    int getval(int rt,int l,int r,int id)
    {
        if(l==r)
        {
            return val[rt];
        }
        int mid=(l+r)>>1;
        if(id<=mid)return getval(L[rt],l,mid,id);
        else return getval(R[rt],mid+1,r,id);
    }
    void show(int v,int n)
    {
        printf("%d: ",v);
        for(int i=1;i<=n;i++)printf("%d ",getval(T[v],1,n,i));
        printf("
    ");
    }
    */
    
    int main()
    {
        int n,m,i,v,op,id,vv;
        scanf("%d%d",&n,&m);
        for(i=1;i<=n;i++)scanf("%d",&a[i]);
        tot=0;
        build(T[0],1,n);//show(0,n);
        for(i=1;i<=m;i++)
        {
            scanf("%d%d%d",&v,&op,&id);
            if(op==1)
            {
                scanf("%d",&vv);
                update(T[i],1,n,T[v],id,vv,1);
            }
            else
            {
                printf("%d
    ",update(T[i],1,n,T[v],id,0,2));
            }
            //show(i,n);
        }
    }
    View Code

     然后是 可持久化并查集

    为此昨晚先去学习了按秩合并的并查集(到现在才知道有这个....)

    hdu1232畅通工程

    //按秩合并 hdu1232 畅通工程
    //http://acm.hdu.edu.cn/showproblem.php?pid=1232
    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1e3+50;
    int pre[maxn],rank[maxn];
    inline int find(int x)
    {
        int r=x,t;
        while(x!=pre[x])x=pre[x];
        while(r!=x){
            t=pre[r];
            pre[r]=x;
            r=t;
        }
        return x;
    }
    inline void unio(int a,int b)
    {
        int fa=find(a);
        int fb=find(b);
        if(rank[fa]<=rank[fb])
        {
            pre[fa]=fb;
            if(rank[fa]==rank[fb])rank[fb]++;
        }
        else pre[fb]=fa;
    }
    int main()
    {
        int n,m,i,j,k,a,b,fa,fb;
        while(~scanf("%d",&n))
        {
            if(!n)break;
            scanf("%d",&m);
            for(i=1;i<=n;i++)pre[i]=i,rank[i]=1;
            for(i=1;i<=m;i++)
            {
                scanf("%d%d",&a,&b);
                unio(a,b);
            }
            int ans=0;
            for(i=1;i<=n;i++)
            {
                if(pre[i]==i)ans++;
            }
            printf("%d
    ",ans-1);
        }
    }
    View Code

    然后今天有精神了终于看着题解(...)做出了 洛谷P3402 可持久化并查集

    #include<bits/stdc++.h>
    #define debug printf("!");
    using namespace std;
    const int maxn=1e5+50;
    
    int n,tot;
    int pre[maxn*40],dep[maxn*40];
    int T[maxn],L[maxn*40],R[maxn*40];
    
    inline void build(int&rt,int l,int r)
    {
        rt=++tot;
        if(l==r)
        {
            pre[rt]=l;
            dep[rt]=1;
            return ;
        }
        int mid=(l+r)>>1;
        build(L[rt],l,mid);
        build(R[rt],mid+1,r);
    }
    inline int query(int rt,int l,int r,int id)
    {
        if(l==r)return rt;
        int mid=(l+r)>>1;
        if(id<=mid)return query(L[rt],l,mid,id);
        else return query(R[rt],mid+1,r,id);
    }
    inline void update(int&rt,int l,int r,int past,int id,int val)
    {
        rt=++tot;
        L[rt]=L[past];R[rt]=R[past];
        if(l==r)
        {
            pre[rt]=val;
            dep[rt]=dep[past];
            return ;
        }
        int mid=(l+r)>>1;
        if(id<=mid)update(L[rt],l,mid,L[past],id,val);
        else update(R[rt],mid+1,r,R[past],id,val);
    }
    
    inline int find(int rt,int id)
    {
        int pnum=query(rt,1,n,id);
        if(pre[pnum]==id)return pnum;
        return find(rt,pre[pnum]);
    }
    
    inline void unio(int id,int a,int b)// id T[id]  a是数值 fa是点标 
    {
        int fa=find(T[id-1],a);
        int fb=find(T[id-1],b);
        if(dep[fa]<=dep[fb])
        {
            //pre[fa]=fb;
            update(T[id],1,n,T[id-1],pre[fa],pre[fb]);//change faid[fa] rt to fb
            if(dep[fa]==dep[fb])dep[fb]++;
        }
        else
        {
            //pre[fb]=fa;
            update(T[id],1,n,T[id-1],pre[fb],pre[fa]);
        }
    }
    int main()
    {
        int m,i,j,k,id,op,a,b,fa,fb;
        scanf("%d%d",&n,&m);
        tot=0;
        build(T[0],1,n);
        for(i=1;i<=m;i++)
        {
            scanf("%d",&op);
            if(op==1)
            {
                scanf("%d%d",&a,&b);
                unio(i,a,b);
            }
            else if(op==2)
            {
                scanf("%d",&k);
                T[i]=T[k];
            }
            else if(op==3)
            {
                scanf("%d%d",&a,&b);
                fa=find(T[i-1],a);
                fb=find(T[i-1],b);
                T[i]=T[i-1];
                if(fa==fb)puts("1");
                else puts("0");
    //            printf("##%d %d
    ",pre[fa],pre[fb]);
            }
        }
    }
    View Code

    2019-09-12 11:26:43

  • 相关阅读:
    表单实现仿淘宝搜索应用
    ASCII字符集
    HTTP状态码
    总结get和post区别
    总结Canvas和SVG的区别
    展示github中的页面(Github Pages)
    canvas的beginPath和closePath分析总结,包括多段弧的情况
    12. Integer to Roman
    13. Roman to Integer
    463. Island Perimeter
  • 原文地址:https://www.cnblogs.com/kkkek/p/11504550.html
Copyright © 2011-2022 走看看