题意:
n<=1e8,m<=1e5.
标程:
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<map> 5 using namespace std; 6 int read() 7 { 8 int x=0,f=1;char ch=getchar(); 9 while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();} 10 while (ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-'0',ch=getchar(); 11 return x*f; 12 } 13 const int N=400005; 14 map<int,int> mp; 15 int sz[N],son[N][2],fa[N],L[N],R[N],tot,id[N],n,rt,m,mn,mx,ans,t,rk,x,y,op,g; 16 void up(int x) {sz[x]=sz[son[x][0]]+sz[son[x][1]]+R[x]-L[x]+1;} 17 void rot(int &k,int x) 18 { 19 int y=fa[x],z=fa[y],l=(son[y][1]==x),r=l^1; 20 if (y==k) k=x;else son[z][(son[z][1]==y)]=x; 21 fa[x]=z;fa[y]=x;fa[son[x][r]]=y; 22 son[y][l]=son[x][r];son[x][r]=y; 23 up(y);up(x); 24 } 25 void spl(int &k,int x) 26 { 27 for (int y;x!=k;rot(k,x)) 28 if ((y=fa[x])!=k) 29 if (son[y][0]==x^son[fa[y]][0]==y) rot(k,x);else rot(k,y); 30 } 31 void ins(int &x,int l,int r,int idx,int f)//在splay的合适位置插入新区间 32 { 33 if (!x) 34 { 35 L[x=++tot]=l;R[tot]=r;id[tot]=idx; 36 sz[tot]=r-l+1;fa[tot]=f; 37 return; 38 } 39 if (r<L[x]) ins(son[x][0],l,r,idx,x); 40 else ins(son[x][1],l,r,idx,x); 41 up(x); 42 } 43 void split(int x,int rk) 44 { 45 if (!son[x][0]) rt=son[x][1],fa[rt]=0;//注意换根,保证splay连通 46 else { 47 int y=son[x][0]; 48 while (son[y][1]) y=son[y][1]; 49 int z=son[x][0];//注意转到根的下一个儿子处,不能转到根 50 spl(z,y); rt=y;fa[rt]=0; 51 if (son[x][1]) son[rt][1]=son[x][1];fa[son[x][1]]=rt; 52 up(rt); 53 } 54 if (L[x]<=rk-1) {ins(rt,L[x],rk-1,L[x],0);spl(rt,tot);}//注意spl前也要判断 55 if (rk+1<=R[x]) {ins(rt,rk+1,R[x],rk+1,0);spl(rt,tot);} 56 } 57 int find_rk(int x,int k)//查询rank为k的点(注意rank为k是离散的,不表示排在第k位) 58 { 59 if (L[x]<=k&&k<=R[x]) return x; 60 if (k<L[x]) return find_rk(son[x][0],k); 61 else return find_rk(son[x][1],k); 62 } 63 int qry_id(int x,int k)//查询rank_k的编号 64 { 65 if (k>sz[son[x][0]]&&k<=sz[x]-sz[son[x][1]]) 66 { 67 if (L[x]==R[x]) return id[x]; 68 else return L[x]+(k-sz[son[x][0]])-1; 69 } 70 if (k<=sz[son[x][0]]) return qry_id(son[x][0],k); 71 else return qry_id(son[x][1],k-(sz[x]-sz[son[x][1]])); 72 } 73 int main() 74 { 75 n=read();m=read(); 76 mn=0;mx=n;ins(rt,1,n,1,0);//mn从-1往下开始标号,以0来区分是否标记。 77 while (m--) 78 { 79 op=read();x=read()-ans; 80 if (op==4) {printf("%d\n",ans=qry_id(rt,x));continue;} 81 rk=mp[x];if (!rk) rk=x; 82 g=find_rk(rt,rk); spl(rt,g); 83 printf("%d\n",t=sz[son[g][0]]+(rk-L[g]+1));//离散排名转实际排名 84 split(g,rk); 85 if (op==1) 86 { 87 y=read()-ans; 88 ins(rt,rk,rk,y,0);spl(rt,tot); 89 mp[y]=rk;mp[x]=0; 90 }else 91 { 92 mp[x]=(op==2)?--mn:++mx; 93 ins(rt,mp[x],mp[x],x,0);spl(rt,tot); 94 } 95 ans=t; 96 } 97 return 0; 98 }
易错点:果然又调了很久,不过感觉自己数据分析能力又提高了。。。
1.spl删点换根的时候注意把y旋转到x的右儿子处。如果把y旋转到x处,那么y的右儿子不一定是x,有可能是z,而z的左儿子是x。
2.注意删点函数split中ins之后的splay也要判断是否在区间限制内,反之有可能tot恰好是被删掉的那一个而产生死循环。
3.mn从-1往下开始标号,以0来区分是否标记。
题解:splay+离散排名+区间分裂
一道splay好题。
一开始在纠结怎么实现排名和编号的双转换?
用mp保存每个点的离散排名(就是给不连续参数代替排名)。splay按照实际排名构造,用sz可以查询区间rank_k。对于splay上的每个点保存一段离散排名区间L~R。
排名转编号:排名->sz查询rank_k的点。
编号转排名:编号->离散排名rk->查找该离散排名所在的splay点g->sz[son[g][0]]+rk-L[g]+1.
一般splay是无法保存下所有节点的。
对于动态修改操作,参考noipDay2T3的做法。
一开始只有一个节点。一个节点中保存编号连续的点L~R。如果L!=R,那么这一段的点都没有修改过。执行一个修改操作,就把原来的一个区间删除修改点断开成两个,再插入修改后的点。
对于2和3操作,如果往前面加入的点,离散排名设为--mn,往后加则是++mx。为了节省map空间,只对修改排名的点记录。
这样时空复杂度就只跟操作有关。
也可以建三棵树,往前面加就扔进1树,不变就在2树,往后面加就扔进3树。这样2树中元素太多,可以记录不在2树中的元素(取补)。好像也可以权值线段树。