线段树进阶学习笔记———动态开点、权值、线段树合并

线段树主要支持三个操作,插入,修改,查询,可能还有一些奇奇怪怪的都在这些范围内

那么原始的线段树还是有一些缺点的

比如,有太多的点没有用到,浪费了太多的空间

 

#include<bits/stdc++.h>
using namespace std;
const int N=1000005;
struct node{
    int sum[N*80],maxn[N*80],minn[N*80];
    int ls[N*80],rs[N*80];
    int seg;
    void init(){
        maxn[0]=-0x7fffffff;
        sum[0]=0;
        minn[0]=0x7fffffff;
    }
    void newpo(int &x){
        x=++seg;
        ls[x]=rs[x]=0;
        sum[x]=0;
        maxn[x]=-0x7fffffff;
        minn[x]=0x7fffffff;
    }
    void pushup(int x){
        sum[x]=sum[ls[x]]+sum[rs[x]];
        maxn[x]=max(maxn[ls[x]],maxn[rs[x]]);
        minn[x]=min(minn[ls[x]],minn[rs[x]]);
    }
    void ins(int &x,int l,int r,int pos,int val){
        if(!x)newpo(x);
        if(l==r){
            sum[x]+=val;
            maxn[x]=pos;
            minn[x]=pos;
            return ;
        }
        int mid=l+r>>1;
        if(pos<=mid)ins(ls[x],l,mid,pos,val);
        else ins(rs[x],mid+1,r,pos,val);
        pushup(x);
        return ;
    }
    int querysum(int x,int l,int r,int ql,int qr){
        //统计区间内一共有多少数
        if(!x)return 0;
        if(ql<=l&&r<=qr)return sum[x];
        int mid=l+r>>1,ret=0;
        if(ql<=mid)ret+=querysum(ls[x],l,mid,ql,qr);
        if(qr>mid)ret+=querysum(rs[x],mid+1,r,ql,qr);
        return ret;
    }
    int querymax(int x,int l,int r,int ql,int qr){
        //统计区间内存在的数的最大值
        if(!x)return -0x7fffffff;
        if(ql<=l&&r<=qr)return maxn[x];
        int mid=l+r>>1,ret=-0x7fffffff;
        if(ql<=mid)ret=max(ret,querymax(ls[x],l,mid,ql,qr));
        if(qr>mid)ret=max(ret,querymax(rs[x],mid+1,r,ql,qr));
        return ret;
    }
    int querymin(int x,int l,int r,int ql,int qr){
        //统计区间内存在的数的最小值
        if(!x)return 0x7fffffff;
        if(ql<=l&&r<=qr)return minn[x];
        int mid=l+r>>1,ret=0x7fffffff;
        if(ql<=mid)ret=min(ret,querymin(ls[x],l,mid,ql,qr));
        if(qr>mid)ret=min(ret,querymin(rs[x],mid+1,r,ql,qr));
        return ret;
    }
}xds;
int main(){
    int n,a[N],rt=0;
    scanf("%d",&n);
    xds.init();
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        xds.ins(rt,1,n,a[i],1);
        printf("sum:%d\nmax:%d\nmin:%d\n",xds.querysum(1,1,n,1,n),xds.querymax(1,1,n,1,n),xds.querymin(1,1,n,1,n));
    }    
}
板子

 

题做的很蒙啊

主要还是对于线段树的理解问题

动态开点线段树

这个很好理解

因为线段树要开四倍空间,很可能会直接把空间炸了

现在提供一种不需要这么大空间的办法。

因为在线段树中,不可能每一个节点都会访问到,尤其是权值线段树中

 

所以不需要全开出来(就是用哪个节点就把哪个节点开出来,不用的就不管了)

我们用一个ls[ ],rs[ ]数组

  ls[ ]表示这个节点的左儿子,代替掉原来的x<<1,

  rs[ ]表示这个节点的右儿子,代替原来的x<<1|1,

这样省去了原来2的指数级的空间

每次更新节点的值时如果没有这个节点就新开一个节点

询问的时候没有就直接返回0(或最小值)(这得看你维护的是啥)

记录一下现在一共有多少个节点,便于开点

int sum[N*80];//需要维护的值
int ls[N*80],rs[N*80];//左右儿子
int seg;//点的个数
int rt[N];//因为你维护的权值与节点值可能不一样,所以要开数组保存这个节点的跟
void update(int &x,int l,int r,int pl,int pr,int k){
//记住节点名,就是x,一定要加&,不然保存不了节点所对的根
    if(!x)x=++seg;//开新点
        if(pl<=l&&r<=pr){
        sum[x]+=k;
        return ;
    }
    int mid=(l+r)>>1;
    if(pl<=mid)update(ls[x],l,mid,pl,pr,k);
    if(pr>mid)update(rs[x],mid+1,r,pl,pr,k);
}
int query(int x,int l,int r,int pos){
    if(!x)return 0;
    if(!pos)return 0;
    int ret=sum[x];
    int mid=l+r>>1;
    if(pos<=mid)ret+=query(ls[x],l,mid,pos);
    else ret+=query(rs[x],mid+1,r,pos);
    return ret;
}
int main(){
        update(rt[x],1,n,val[x]);
        query(rt[x],1,n,val[x]);
}   
动态开点代码

 千万千万要记得加&,要不然会死人的

权值线段树

这东西你确定需要我给你讲???????

就维护的不是点而是这个点的权值

对就这就这。。。。。。

不明白就去做题,做着做着就明白了。。昂

举个例子

给你一堆数1 2 5 8  1 3 45 4 8 6 23 5 4 8 6 3 2 14 5 3 21 2 321 3

拿出一个数来要你统计这个数在这个序列中是第几大的数

这时候就可以将这些数插入到线段树中,将这些数的值插入进去!!!

sum[ ]记的是这个区间内的数的个数

询问的时候就找这个数之前有多少数就可以

query(rt[x],1,max,1,val[x]);

在权值线段树中,因为int的范围动辄1e9,所以我们就要用得到动态开点

合并线段树!!!!!!!!!!!

这个东西非常的重要啊

将两课线段树的权值合并起来

合并是也要看你维护的是什么,最大值或者是权值和或者数量

就题论题。。。

在我看来线段树合并一般维护的是权值线段树,当然也可以维护一个桶,这个要就题论题了

还是上一个数列的例子

这时给你很多个这样的数列;

要求你将哪两个数列合并

然后再求某个数在这个合并后的序列中的次序

这时候还是上面的那个权值线段树,只不过这次要开n个,因为有n个序列

这时候空间就炸了,就要动态开点了

合并就是将两个线段树内相同区间内的sum[ ]相加

int marge(int x,int y){
    if(!x)return y;
    if(!y)return x;//动态开点,如果另外一个子树不存在就返回别的子树
    sum[x]+=sum[y];//相加
    ls[x]=marge(ls[x],ls[y]);
    rs[x]=marge(rs[x],rs[y]);
    return x;
}//返回合并后的线段树的根节点
线段树合并

Promotion Counting

这个题和我在上面说的序列的操作有些相似,只不过是在树上操作而已

这时候要用到线段树合并了,你不能直接向同一颗线段树中插入,因为你不知道要插入的顺序,而且插入之后还要拔出来

还要在插进去,同时我们维护的是一个权值线段树,这样就可以统计比他小的个数了

然后不停的由儿子向父亲合并,得到答案

还要动态开点,因为每个点的权值只有一个,能用到的点只有logn个

 1 #include<bits/stdc++.h>
 2 #include<iostream>
 3 using namespace std;
 4 #define re register int
 5 const int N=100005;
 6 int n;
 7 int p[N],d[N];
 8 int to[N],nxt[N],head[N],rp;
 9 int rt[N],ans[N];
10 bool cmp(int x,int y){return p[x]<p[y];}
11 void add_edg(int x,int y){
12     to[++rp]=y;
13     nxt[rp]=head[x];
14     head[x]=rp;
15 }
16 struct node{
17     int sum[N*80];
18     int ls[N*80],rs[N*80];
19     int cnt;
20     int insert(int x,int l,int r,int pos){
21         if(!x)x=++cnt;
22         sum[x]++;
23         if(l==r)return x;
24         int mid=l+r>>1;
25         if(pos<=mid)ls[x]=insert(ls[x],l,mid,pos);
26         else rs[x]=insert(rs[x],mid+1,r,pos);
27         sum[x]=sum[ls[x]]+sum[rs[x]];
28         return x;
29     }
30     int query(int x,int l,int r,int pos){
31         if(!x)return 0;
32         if(l==r)return sum[x];
33         int mid=(l+r)>>1;
34         if(pos<=mid)return query(ls[x],l,mid,pos)+sum[rs[x]];
35         else return query(rs[x],mid+1,r,pos);
36     }
37     int marge(int x,int y,int l,int r){
38         if(!x)return y+x;
39         if(!y)return x+y;
40         if(l==r){
41             sum[x]+=sum[y];
42             return x;
43         }
44         int mid=(l+r)>>1;
45         ls[x]=marge(ls[x],ls[y],l,mid);
46         rs[x]=marge(rs[x],rs[y],mid+1,r);
47         sum[x]=sum[ls[x]]+sum[rs[x]];
48         return x;
49     }
50 }xds;
51 void dfs(int x){
52     for(re i=head[x];i;i=nxt[i])
53         dfs(to[i]);
54     rt[x]=xds.insert(rt[x],1,n+1,p[x]);
55     for(re i=head[x];i;i=nxt[i])
56         rt[x]=xds.marge(rt[x],rt[to[i]],1,n+1);;
57     ans[x]=xds.query(rt[x],1,n+1,p[x]+1);
58 }
59 int main(){
60     scanf("%d",&n);
61     for(re i=1;i<=n;i++)
62         scanf("%d",&p[i]),d[i]=i;
63     sort(d+1,d+n+1,cmp);
64     for(re i=1;i<=n;i++)p[d[i]]=i;
65     for(re i=2;i<=n;i++){
66         int f;
67         scanf("%d",&f);
68         add_edg(f,i);
69     }
70     dfs(1);
71     for(re i=1;i<=n;i++){
72         printf("%d\n",ans[i]);
73     }
74 }
Promotion Counting

雨天的尾巴

这好象是网上公认的板子题了吧

这题要用到树链剖分的重儿子的思想,主要因为我们要一条条链的去更新

然后还有一个树上差分的思想,

还有LCA,利用树链剖分完之后的top,其实倍增也可以;

最后线段树合并,每次维护出来一个点的最大数量的权值

 

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 #define re register int
  4 const int N=100005;
  5 int n,m;
  6 int lc[N],rc[N],vc[N];
  7 int to[N<<1],nxt[N<<1],head[N],rp;
  8 int son[N],fa[N],siz[N],top[N],dep[N];
  9 int rt[N],ans[N],R;
 10 void add_edg(int x,int y){
 11     to[++rp]=y;
 12     nxt[rp]=head[x];
 13     head[x]=rp;
 14 }
 15 void dfs1(int x){
 16     siz[x]=1;
 17     son[x]=-1;
 18     for(re i=head[x];i;i=nxt[i]){
 19         int y=to[i];
 20         if(dep[y])continue;
 21         dep[y]=dep[x]+1;
 22         fa[y]=x;
 23         dfs1(y);
 24         siz[x]+=siz[y];
 25         if(son[x]==-1||siz[y]>siz[son[x]])son[x]=y;
 26     }
 27 }
 28 void dfs2(int x,int f){
 29     top[x]=f;
 30     if(son[x]==-1)return ;
 31     dfs2(son[x],f);
 32     for(re i=head[x];i;i=nxt[i]){
 33         int y=to[i];
 34         if(y!=son[x]&&y!=fa[x])
 35             dfs2(y,y);
 36     }
 37 }
 38 int LCA(int x,int y){
 39     int fx=top[x],fy=top[y];
 40     while(fx!=fy){
 41         if(dep[fx]<dep[fy])
 42             swap(x,y),swap(fx,fy);
 43         x=fa[fx];
 44         fx=top[x];
 45     }
 46     if(dep[x]<dep[y])return x;
 47     return y;
 48 }
 49 struct node{
 50     int sum[N*60],typ[N*60];
 51     int ls[N*60],rs[N*60];
 52     int seg;
 53     void pushup(int x){
 54         if(sum[ls[x]]>=sum[rs[x]])
 55             sum[x]=sum[ls[x]],typ[x]=typ[ls[x]];
 56         else
 57             sum[x]=sum[rs[x]],typ[x]=typ[rs[x]];
 58     }
 59     int update(int x,int l,int r,int pos,int k){
 60         if(!x)x=++seg;
 61         if(l==r){
 62             sum[x]+=k;
 63             typ[x]=pos;
 64             return x;
 65         }
 66         int mid=(l+r)>>1;
 67         if(pos<=mid)ls[x]=update(ls[x],l,mid,pos,k);
 68         else rs[x]=update(rs[x],mid+1,r,pos,k);
 69         pushup(x);
 70         return x;
 71     }
 72     int merge(int x,int y,int l,int r){
 73         if(!x)return y;
 74         if(!y)return x;
 75         if(l==r){
 76             sum[x]+=sum[y];
 77             typ[x]=l;
 78             return x;
 79         }
 80         int mid=(l+r)>>1;
 81         ls[x]=merge(ls[x],ls[y],l,mid);
 82         rs[x]=merge(rs[x],rs[y],mid+1,r);
 83         pushup(x);
 84         return x;
 85     }
 86 }xds;
 87 void getans(int x){
 88     for(re i=head[x];i;i=nxt[i]){
 89         int y=to[i];
 90         if(dep[y]>dep[x]){
 91             getans(y);
 92             rt[x]=xds.merge(rt[x],rt[y],1,R);
 93         }
 94     }
 95     if(xds.sum[rt[x]])ans[x]=xds.typ[rt[x]];
 96 }
 97 int main(){
 98     scanf("%d%d",&n,&m);
 99     for(re i=1,x,y;i<n;i++){
100         scanf("%d%d",&x,&y);
101         add_edg(x,y);
102         add_edg(y,x);
103     }
104     dep[1]=1;
105     dfs1(1);
106     dfs2(1,1);
107     for(re i=1;i<=m;i++){
108         scanf("%d%d%d",&lc[i],&rc[i],&vc[i]);
109         R=max(R,vc[i]);
110     }
111     for(re i=1;i<=m;i++){
112         int lca=LCA(lc[i],rc[i]);
113         rt[lc[i]]=xds.update(rt[lc[i]],1,R,vc[i],1);
114         rt[rc[i]]=xds.update(rt[rc[i]],1,R,vc[i],1);
115         rt[lca]=xds.update(rt[lca],1,R,vc[i],-1);
116         if(fa[lca])rt[fa[lca]]=xds.update(rt[fa[lca]],1,R,vc[i],-1);
117     }
118     getans(1);
119     for(re i=1;i<=n;i++)
120         printf("%d\n",ans[i]);
121 }
雨天的尾巴

 noip模拟测试6  T2

posted @ 2021-04-23 16:09  fengwu2005  阅读(353)  评论(6编辑  收藏  举报