http://www.lydsy.com/JudgeOnline/problem.php?id=3224
基础的平衡树操作。对序列中单点修改、求第k小数、求某个数的排名、查询某个数的前驱后继。
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <algorithm> #define MAXN 100010 #define lson node[o].lc #define rson node[o].rc using namespace std; struct Treap { struct Node { int lc,rc; int val,key; //val=该点的键值(平衡二叉树用),key=该点的权值(Heap用) int cnt; //该点对应的相同数字个数 int size; //该点对应的子树大小 }node[MAXN]; int nCount,root; //结点总个数、根节点编号 void pushup(int o) //维护结点o { node[o].size=node[lson].size+node[rson].size+node[o].cnt; } void rotateR(int &o) //右旋结点o { int tmp=lson; lson=node[tmp].rc; node[tmp].rc=o; node[tmp].size=node[o].size; pushup(o); o=tmp; } void rotateL(int &o) //左旋结点o { int tmp=rson; rson=node[tmp].lc; node[tmp].lc=o; node[tmp].size=node[o].size; pushup(o); o=tmp; } void insert(int &o,int x) //在根节点为o的子树下插入键值为x的点 { if(o==0) //空树,建立新的节点 { o=++nCount; node[o].size=node[o].cnt=1; node[o].key=rand()*rand(); node[o].val=x; return; } node[o].size++; if(x<node[o].val) //x比o的键值小,往左子树插入 { insert(lson,x); if(node[o].key>node[lson].key) //维护小根堆的性质 rotateR(o); } else if(x>node[o].val) //x比o的键值大,往右子树插入 { insert(rson,x); if(node[o].key>node[rson].key) //维护小根堆的性质 rotateL(o); } else //x与o的键值相同 node[o].cnt++; } void del(int &o,int x) //在根节点为o的子树下删除键值为x的点 { if(o==0) return; if(node[o].val==x) //o的键值就是x { if(node[o].cnt>1) //o对应的相同数字不止一个 { node[o].size--; node[o].cnt--; } //o只对应一个数字,删掉这个数字,结点o就没了 else if(lson==0||rson==0) //o只有一个儿子 o=lson+rson; else if(node[lson].key<node[rson].key) //左子树权值小 { rotateR(o); //把o旋到左子树里去 del(o,x); } else //右子树权值小 { rotateL(o); //把o旋到右子树里去 del(o,x); } return; } node[o].size--; if(x<node[o].val) del(lson,x); else del(rson,x); } int getRank(int o,int x) //求键值x在树o中的排名 { if(o==0) return 0; if(node[o].val==x) return node[lson].size+1; //o的键值就是x if(node[o].val<x) return node[lson].size+node[o].cnt+getRank(rson,x); return getRank(lson,x); } int getNumFromRank(int o,int kth) //求树中第k小的数 { if(o==0) return 0; if(kth<=node[lson].size) return getNumFromRank(lson,kth); else if(kth>node[lson].size+node[o].cnt) return getNumFromRank(rson,kth-node[lson].size-node[o].cnt); else return node[o].val; } void getBefore(int o,int x,int &ans) //在树o中求键值为x的结点的前驱 { if(o==0) return; if(node[o].val<x) { ans=node[o].val; getBefore(rson,x,ans); } else getBefore(lson,x,ans); } void getAfter(int o,int x,int &ans) //在树o中求键值为x的结点的后继 { if(o==0) return; if(node[o].val>x) { ans=node[o].val; getAfter(lson,x,ans); } else getAfter(rson,x,ans); } }treap; int main() { srand(23333); int n; scanf("%d",&n); for(int i=1;i<=n;i++) { int cmd,x; scanf("%d%d",&cmd,&x); if(cmd==1) //插入操作 treap.insert(treap.root,x); else if(cmd==2) //删除操作 treap.del(treap.root,x); else if(cmd==3) //求排名 printf("%d\n",treap.getRank(treap.root,x)); else if(cmd==4) //已知排名求数字 printf("%d\n",treap.getNumFromRank(treap.root,x)); else if(cmd==5) //求前驱 { int ans=0; treap.getBefore(treap.root,x,ans); printf("%d\n",ans); } else if(cmd==6) //求后继 { int ans=0; treap.getAfter(treap.root,x,ans); printf("%d\n",ans); } } return 0; }
http://www.lydsy.com/JudgeOnline/problem.php?id=3223
要求支持对序列进行翻转操作,并输出最终的序列。给Splay打上翻转标记rev即可。建议将长度为n的序列1~n转变为长度为n+2的序列1~n+2,最左边的数字1和最右边的数字n+2起保护作用,真正的被操作序列是2~n+1。
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <algorithm> #define MAXN 100100 #define lson node[o].ch[0] #define rson node[o].ch[1] using namespace std; struct Splay { struct node { int fa; //父结点 bool rev; //翻转标记 int size; //子树大小 int val; //结点键值 int ch[2]; //左右儿子 }node[MAXN]; int nCount,root; //开的结点个数、根节点 int id[MAXN]; //初始时的序列 void pushup(int o) //上传size标记 { node[o].size=node[lson].size+node[rson].size+1; } void pushdown(int o) //下传rev标记 { if(node[o].rev) { swap(lson,rson); //交换左右儿子 node[lson].rev^=1; node[rson].rev^=1; node[o].rev=0; } } void build(int L,int R,int fa) //建立区间[L,R]的结点,该结点对应子树的父亲是fa { if(L>R) return; int now=L,last=fa; if(L==R) { node[L].fa=fa; node[L].size=1; node[L].val=id[L]; if(L<fa) node[fa].ch[0]=L; else node[fa].ch[1]=L; return; } int M=(L+R)>>1; build(L,M-1,M); build(M+1,R,M); node[M].fa=fa; node[M].val=id[M]; pushup(M); if(M<fa) node[fa].ch[0]=M; else node[fa].ch[1]=M; } void rotate(int x,int &k) //把x旋转到其上一层去,不能超过k的高度 { int y=node[x].fa; int z=node[y].fa; int p,q; if(node[y].ch[0]==x) p=0; else p=1; q=p^1; if(y==k) k=x; else { if(node[z].ch[0]==y) node[z].ch[0]=x; else node[z].ch[1]=x; } node[x].fa=z; node[y].fa=x; node[node[x].ch[q]].fa=y; node[y].ch[p]=node[x].ch[q]; node[x].ch[q]=y; pushup(y); pushup(x); } void splay(int x,int &k) //把x伸展到点k去 { while(x!=k) { int y=node[x].fa; int z=node[y].fa; if(y!=k) { if((node[y].ch[0]==x)==(node[z].ch[0]==y)) rotate(y,k); else rotate(x,k); } rotate(x,k); } } int getKth(int o,int kth) //在子树o中找第k小数 { pushdown(o); if(node[lson].size+1==kth) return node[o].val; //o就是第k小数 else if(node[lson].size>=kth) return getKth(lson,kth); else return getKth(rson,kth-node[lson].size-1); } int reverse(int L,int R) //翻转区间(L,R) { int x=getKth(root,L),y=getKth(root,R+2); splay(x,root); splay(y,node[x].ch[1]); int z=node[y].ch[0]; //z子树对应区间(L,R) node[z].rev^=1; } }spt; int main() { int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=n+2;i++) spt.id[i]=i; spt.build(1,n+2,0); spt.root=(n+3)>>1; for(int i=1;i<=m;i++) { int L,R; scanf("%d%d",&L,&R); spt.reverse(L,R); } for(int i=2;i<=n+1;i++) printf("%d ",spt.getKth(spt.root,i)-1); return 0; }
http://www.lydsy.com/JudgeOnline/problem.php?id=1036
要求支持对一棵有点权、无边权的静态无根树进行修改点权操作,并支持查询任意两点之间最短路上的最大点权和点权和。
裸树链剖分+套线段树或者BIT。
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <algorithm> #define MAXN 31000 #define INF 0x3f3f3f3f #define lowbit(x) ((x)&(-(x))) using namespace std; struct edge { int u,v,next; }edges[MAXN*2]; int head[MAXN],nCount=0; void AddEdge(int U,int V) //添加有向边U->V { edges[++nCount].u=U; edges[nCount].v=V; edges[nCount].next=head[U]; head[U]=nCount; } void add(int U,int V) //添加无向边U-V { AddEdge(U,V); AddEdge(V,U); } int depth[MAXN],fa[MAXN],size[MAXN],son[MAXN]; //depth[i]=点i的深度,fa[i]=树中点i的父亲,size[i]=子树i的大小,son[i]=点i的重儿子 int hash[MAXN],num=0; //hash[i]=点i在线段树中的编号 int top[MAXN]; //top[i]=点i所属重链的祖先 int w[MAXN]; void DFS(int u,int dep) //第一次DFS找重儿子 { size[u]=1; son[u]=0; depth[u]=dep; for(int p=head[u];p!=-1;p=edges[p].next) { int v=edges[p].v; if(v==fa[u]) continue; fa[v]=u; DFS(v,dep+1); size[u]+=size[v]; if(size[v]>size[son[u]]) son[u]=v; } } void BuildTree(int u,int anc) //第二次DFS找重链 { hash[u]=++num; top[u]=anc; if(son[u]) BuildTree(son[u],anc); for(int p=head[u];p!=-1;p=edges[p].next) { int v=edges[p].v; if(v==fa[u]||v==son[u]) continue; BuildTree(v,v); } } struct SegmentTree { int maxv[4*MAXN],sumv[4*MAXN]; void pushup(int o) //上传标记 { sumv[o]=sumv[o*2]+sumv[o*2+1]; maxv[o]=max(maxv[o*2],maxv[o*2+1]); } void update(int pos,int x,int L,int R,int o) //当前结点o对应区间[L,R],将pos位置的数字修改为x { if(L==R) { maxv[o]=sumv[o]=x; return; } int M=(L+R)>>1; if(pos<=M) update(pos,x,L,M,o*2); else update(pos,x,M+1,R,o*2+1); pushup(o); } int QueryMax(int ql,int qr,int L,int R,int o) //求[ql,qr]最值 { if(ql==L&&qr==R) return maxv[o]; int M=(L+R)>>1; if(qr<=M) return QueryMax(ql,qr,L,M,o*2); else if(ql>M) return QueryMax(ql,qr,M+1,R,o*2+1); else return max(QueryMax(ql,M,L,M,o*2),QueryMax(M+1,qr,M+1,R,o*2+1)); } int QuerySum(int ql,int qr,int L,int R,int o) //求[ql,qr]区间和 { if(ql==L&&qr==R) return sumv[o]; int M=(L+R)>>1; if(qr<=M) return QuerySum(ql,qr,L,M,o*2); else if(ql>M) return QuerySum(ql,qr,M+1,R,o*2+1); else return QuerySum(ql,M,L,M,o*2)+QuerySum(M+1,qr,M+1,R,o*2+1); } }segtree; int QueryMax(int a,int b,int n) //求a到b的路径上的最大点权,整棵树有n个点 { int ta=top[a],tb=top[b],ans=-INF; while(ta!=tb) { if(depth[ta]<depth[tb]) { swap(ta,tb); swap(a,b); } ans=max(ans,segtree.QueryMax(hash[ta],hash[a],1,n,1)); a=fa[ta]; //爬到上一个链上 ta=top[a]; } if(depth[a]>depth[b]) swap(a,b); return max(ans,segtree.QueryMax(hash[a],hash[b],1,n,1)); } int QuerySum(int a,int b,int n) //求a到b的路径上的点权和,整棵树有n个点 { int ta=top[a],tb=top[b],ans=0; while(ta!=tb) { if(depth[ta]<depth[tb]) { swap(ta,tb); swap(a,b); } ans+=segtree.QuerySum(hash[ta],hash[a],1,n,1); a=fa[ta]; //爬到上一个链上 ta=top[a]; } if(depth[a]>depth[b]) swap(a,b); return ans+=segtree.QuerySum(hash[a],hash[b],1,n,1); } int main() { memset(head,-1,sizeof(head)); int n; char cmd[10]; scanf("%d",&n); for(int i=1;i<n;i++) { int u,v; scanf("%d%d",&u,&v); add(u,v); } for(int i=1;i<=n;i++) scanf("%d",&w[i]); DFS(1,1); BuildTree(1,1); for(int i=1;i<=n;i++) segtree.update(hash[i],w[i],1,n,1); int q; scanf("%d",&q); while(q--) { int a,b; scanf("%s%d%d",cmd,&a,&b); if(cmd[1]=='M') //QMAX printf("%d\n",QueryMax(a,b,n)); else if(cmd[1]=='S') //QSUM printf("%d\n",QuerySum(a,b,n)); else segtree.update(hash[a],b,1,n,1); } return 0; }
http://www.lydsy.com/JudgeOnline/problem.php?id=2631
一道很好的LCT练手题。注意打标记的问题,由于此题中同时存在区间乘和区间加标记,因此要注意如何更新这两个标记,我们可以约定同时存在区间乘和区间加标记时,先区间同时乘,然后再进行区间加。如果一个区间要乘上mulv后再加上addv,而其原有的区间和为sumv[o],原有的区间乘和区间加标记为mul[o]和add[o],那么此次修改操作后这个区间的区间和应为[(sumv[o]*mul[o]+add[o])*mulv+addv]=sumv[o]*(mul[o]*mulv)+(add[o]*mulv+addv),因此该区间新的区间乘标记为(mul[o]*mulv),新的区间加标记为(add[o]*mulv+addv)
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <algorithm> #define MAXN 102000 #define MOD 51061 #define lson ch[o][0] #define rson ch[o][1] using namespace std; typedef unsigned int uint; struct LinkCutTree { int nCount; //开的结点个数 int ch[MAXN][2],fa[MAXN]; //Splay的儿子标记 bool rev[MAXN]; //Splay的翻转标记 int size[MAXN]; //Splay的子树大小标记 uint sum[MAXN],mul[MAXN],add[MAXN],val[MAXN]; //Splay的区间和标记、区间乘法标记、区间加法标记、本节点的值 int q[MAXN],top; //伸展时用的栈 bool isRoot(int x) //判断x是否是Splay的根节点 { return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x; } void pushup(int o) //上传标记操作 { sum[o]=sum[lson]+sum[rson]+val[o]; sum[o]%=MOD; size[o]=size[lson]+size[rson]+1; size[o]%=MOD; //???? } void cal(int o,int mulv,int addv) //点o对应的区间先乘上mulv后加上addv { if(!o) return; val[o]=(val[o]*mulv+addv)%MOD; sum[o]=(sum[o]*mulv+addv*size[o])%MOD; add[o]=(add[o]*mulv+addv)%MOD; mul[o]=(mul[o]*mulv)%MOD; } void pushdown(int o) //下传add、mul、rev标记 { if(rev[o]) //需要翻转o对应区间 { rev[o]^=1; rev[lson]^=1; rev[rson]^=1; swap(lson,rson); } if(mul[o]!=1||add[o]!=0) //本节点的mul和add标记没清 { cal(lson,mul[o],add[o]); cal(rson,mul[o],add[o]); } mul[o]=1; add[o]=0; } void rotate(int x) //旋转x到其上一层去 { int y=fa[x],z=fa[y],p,q; if(ch[y][0]==x) p=0; else p=1; q=p^1; if(!isRoot(y)) { if(ch[z][0]==y) ch[z][0]=x; else ch[z][1]=x; } fa[x]=z; fa[y]=x; fa[ch[x][q]]=y; ch[y][p]=ch[x][q]; ch[x][q]=y; pushup(y); pushup(x); } void splay(int x) //将x旋转到根节点上去 { top=0; q[++top]=x; for(int i=x;!isRoot(i);i=fa[i]) q[++top]=fa[i]; for(int i=top;i;i--) pushdown(q[i]); while(!isRoot(x)) { int y=fa[x],z=fa[y]; if(!isRoot(y)) { if((ch[y][0]==x)==(ch[z][0]==y)) rotate(y); else rotate(x); } rotate(x); } } void access(int x) //打通x到根节点的偏爱路径 { int tmp=0; while(x) { splay(x); ch[x][1]=tmp; pushup(x); tmp=x; x=fa[x]; } } void makeroot(int x) //使x成为根 { access(x); splay(x); rev[x]^=1; } void link(int x,int y) //连接x、y,使得y成为x的父亲 { makeroot(x); fa[x]=y; } void cut(int x,int y) //断开x、y的连接,原来y是x的父亲 { makeroot(x); access(y); splay(y); ch[y][0]=fa[x]=0; } }lct; int main() { int n,m; scanf("%d%d",&n,&m); for(int i=1;i<n;i++) //初始的树 { int u,v; scanf("%d%d",&u,&v); lct.link(u,v); } for(int i=1;i<=n;i++) lct.val[i]=lct.sum[i]=lct.mul[i]=lct.size[i]=1; for(int i=1;i<=m;i++) { char cmd[10]; int x,y,addv,mulv; scanf("%s%d%d",cmd,&x,&y); if(cmd[0]=='+') //x到y的路径上的点权统一加上addv { scanf("%d",&addv); lct.makeroot(x); lct.access(y); lct.splay(y); lct.cal(y,1,addv); } else if(cmd[0]=='-') //断开x和y的连接,重新连新的边 { lct.cut(x,y); scanf("%d%d",&x,&y); lct.link(x,y); } else if(cmd[0]=='*') //x到y的路径上的点权统一乘上addv { scanf("%d",&mulv); lct.makeroot(x); lct.access(y); lct.splay(y); lct.cal(y,mulv,0); } else if(cmd[0]=='/') //求x到y的路径上的点权和 { lct.makeroot(x); lct.access(y); lct.splay(y); printf("%d\n",lct.sum[y]); } } return 0; }