线段树进阶学习笔记———动态开点、权值、线段树合并
线段树主要支持三个操作,插入,修改,查询,可能还有一些奇奇怪怪的都在这些范围内
那么原始的线段树还是有一些缺点的
比如,有太多的点没有用到,浪费了太多的空间
#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 }
雨天的尾巴
这好象是网上公认的板子题了吧
这题要用到树链剖分的重儿子的思想,主要因为我们要一条条链的去更新
然后还有一个树上差分的思想,
还有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 }