Scx117
只一眼,便辽阔了时间。

题意:

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树中的元素(取补)。好像也可以权值线段树。

posted on 2018-04-06 07:58  Scx117  阅读(137)  评论(0编辑  收藏  举报