【线段树】主席树学习笔记

摘:

主席树思想是每个位置都维护一个线段树,线段树的节点是值的范围,然后第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

原文地址:https://www.cnblogs.com/kkkek/p/11504550.html