方伯伯的 OJ
方伯伯正在做他的 OJ。现在他在处理 OJ 上的用户排名问题。
OJ 上注册了 (n) 个用户,编号为 (1 sim n),一开始他们按照编号排名。方伯伯会按照心情对这些用户做以下四种操作,修改用户的排名和编号:
- 操作格式为
1 x y
,意味着将编号为 (x) 的用户编号改为 (y),而排名不变,执行完该操作后需要输出该用户在排名中的位置,数据保证 (x) 必然出现在排名中,同时 (y) 是一个当前不在排名中的编号。 - 操作格式为
2 x
,意味着将编号为 (x) 的用户的排名提升到第一位,执行完该操作后需要输出执行该操作前编号为 (x) 用户的排名。 - 操作格式为
3 x
,意味着将编号为 (x) 的用户的排名降到最后一位,执行完该操作后需要输出执行该操作前编号为 (x) 用户的排名。 - 操作格式为
4 k
,意味着查询当前排名为 (k) 的用户编号,执行完该操作后需要输出当前操作用户的编号。
但同时为了防止别人监听自己的工作,方伯伯对他的操作进行了加密,即将四种操作的格式分别改为了:
1 x+a y+a
2 x+a
3 x+a
4 k+a
其中 (a) 为上一次操作得到的输出,一开始 (a=0)。
对于 (100 \%) 的数据,(1 leq n leq 10^8, 1 leq m leq 10^5)。
题解
这题只需要维护两个映射就行了,它们分别是:
-
splay节点到编号区间,这个直接存在splay节点上就行了。
-
编号区间到splay节点,这个用map套
pair<int,int>
就行了。
但是这道题只会把单个元素移动到开头结尾,所以这样做有些大材小用了。
https://www.cnblogs.com/ZH-comld/p/9715245.html
https://www.luogu.com.cn/blog/a23333/scoi2014-fang-bo-bo-di-oj-xian-duan-shu-ping-heng-shu
只需要用动态开点线段树维护([1-m,n+m])这些可能用到的位置的空缺,然后用两个map维护标号到位置和位置到标号的映射就行了。
时间复杂度(O(mlog n))。
CO int N=1e5+10;
int L,R;
map<int,int> pos,idx;
int root,tot;
int lc[N*30],rc[N*30],sum[N*30];
#define mid ((l+r)>>1)
void insert(int&x,int l,int r,int p){
if(!x) x=++tot;
++sum[x];
if(l==r) return;
if(p<=mid) insert(lc[x],l,mid,p);
else insert(rc[x],mid+1,r,p);
}
int query(int x,int l,int r,int p){
if(!sum[x] or l==r) return 0;
if(p<=mid) return query(lc[x],l,mid,p);
else return sum[lc[x]]+query(rc[x],mid+1,r,p);
}
int find(int x,int l,int r,int k){
if(l==r) return l;
int num=max(0,min(mid,R)-max(l,L)+1-sum[lc[x]]);
if(num>=k) return find(lc[x],l,mid,k);
else return find(rc[x],mid+1,r,k-num);
}
#undef mid
int main(){
int n=read<int>(),m=read<int>();
L=1,R=n;
int ans=0;
for(int i=1;i<=m;++i){
int opt=read<int>();
if(opt==1){
int x=read<int>()-ans,y=read<int>()-ans;
int p=pos.count(x)?pos[x]:x;
ans=p-L+1-query(root,1-m,n+m,p);
pos[y]=p,idx[p]=y,
printf("%d
",ans);
}
else if(opt==2){
int x=read<int>()-ans;
int p=pos.count(x)?pos[x]:x;
ans=p-L+1-query(root,1-m,n+m,p);
insert(root,1-m,n+m,p);
pos[x]=--L,idx[L]=x;
printf("%d
",ans);
}
else if(opt==3){
int x=read<int>()-ans;
int p=pos.count(x)?pos[x]:x;
ans=p-L+1-query(root,1-m,n+m,p);
insert(root,1-m,n+m,p);
pos[x]=++R,idx[R]=x;
printf("%d
",ans);
}
else{
int k=read<int>()-ans;
int p=find(root,1-m,n+m,k);
ans=idx.count(p)?idx[p]:p;
printf("%d
",ans);
}
}
return 0;
}
列队
Sylvia 是一个热爱学习的女孩子。
前段时间, Sylvia 参加了学校的军训。众所周知,军训的时候需要站方阵。 Sylvia 所在的方阵中有 (n imes m) 名学生,方阵的行数为 (n),列数为 (m)。
为了便于管理,教官在训练开始时,按照从前到后,从左到右的顺序给方阵中的学生从 (1) 到 (n imes m) 编上了号码(参见后面的样例)。即:初始时,第 (i) 行第 (j) 列的学生的编号是 ((i − 1) imes m + j)。
然而在练习方阵的时候,经常会有学生因为各种各样的事情需要离队。在一天中,一共发生了 (q) 件这样的离队事件。每一次离队事件可以用数对 ((x, y) (1le xle n, 1le yle m)) 描述, 表示第 (x) 行第 (y) 列的学生离队。
在有学生离队后,队伍中出现了一个空位。为了队伍的整齐,教官会依次下达这样的两条指令:
-
向左看齐。这时第一列保持不动,所有学生向左填补空缺。不难发现在这条指令之后,空位在第 (x) 行第 (m) 列。
-
向前看齐。这时第一行保持不动,所有学生向前填补空缺。不难发现在这条指令之后,空位在第 (n) 行第 (m) 列。
教官规定不能有两个或更多学生同时离队。即在前一个离队的学生归队之后,下一个学生才能离队。因此在每一个离队的学生要归队时,队伍中有且仅有第 (n) 行第 (m) 列一个空位,这时这个学生会自然地填补到这个位置。
因为站方阵真的很无聊,所以 Sylvia 想要计算每一次离队事件中,离队的同学的编号是多少。
注意:每一个同学的编号不会随着离队事件的发生而改变,在发生离队事件后方阵中同学的编号可能是乱序的。
(n,m,q leq 3 imes 10^5)。
题解
https://www.luogu.com.cn/blog/rqy/solution-p3960
我们观察每一行除了最后一个数之外的数在操作中是如何变化的。
如果出队在((x,y)),那么第(x)行(除了最后一个数)会弹出第(y)个数,它后面的数依次左移,再在最后插入一个数(就是最后一列当前的第(x)个数);然后,对于最后一列,我们要弹出第(x)个数(插入到第(x)行),再在最后插入一个数(就是刚出队的那个)。
简单来说,就是第(x)行中间删一个,末尾加一个。最后一列中间删一个,末尾加一个。
同样有高端splay做法。由于这题我们不需要编号到节点的映射,所以要简单很多。我们只需要对每一行和最后一列维护splay节点到编号区间的映射即可。
同样这题只会把元素移动到结尾,所以直接动态开点线段树维护空缺,然后map维护位置到标号的映射就行了。
然而……十年OI一场空,不开long long
见祖宗。说起来我两次写这题都忘了开long long
了。
CO int N=3e5+10;
int R[N];
map<int,int64> idx[N];
int root[N],tot;
int lc[N*40],rc[N*40],sum[N*40];
#define mid ((l+r)>>1)
void insert(int&x,int l,int r,int p){
if(!x) x=++tot;
++sum[x];
if(l==r) return;
if(p<=mid) insert(lc[x],l,mid,p);
else insert(rc[x],mid+1,r,p);
}
int find(int x,int l,int r,int k,int R){
if(l==r) return l;
int num=max(0,min(mid,R)-l+1-sum[lc[x]]);
if(num>=k) return find(lc[x],l,mid,k,R);
else return find(rc[x],mid+1,r,k-num,R);
}
#undef mid
int main(){
int n=read<int>(),m=read<int>(),Q=read<int>();
fill(R+1,R+n+1,m-1),R[n+1]=n;
for(int i=1;i<=Q;++i){
int x=read<int>(),y=read<int>();
if(y==m){
int p=find(root[n+1],1,n+Q,x,R[n+1]);
int64 u=idx[n+1].count(p)?idx[n+1][p]:(int64)p*m;
printf("%lld
",u);
insert(root[n+1],1,n+Q,p);
idx[n+1][++R[n+1]]=u;
continue;
}
int p=find(root[x],1,m+Q,y,R[x]);
int64 u=idx[x].count(p)?idx[x][p]:(int64)(x-1)*m+p;
printf("%lld
",u);
int q=find(root[n+1],1,n+Q,x,R[n+1]);
int64 v=idx[n+1].count(q)?idx[n+1][q]:(int64)q*m;
insert(root[x],1,m+Q,p);
idx[x][++R[x]]=v;
insert(root[n+1],1,n+Q,q);
idx[n+1][++R[n+1]]=u;
}
return 0;
}