[NOIP]2017列队——旋转treap/非旋转treap

时间:2023-03-09 07:21:24
[NOIP]2017列队——旋转treap/非旋转treap

Sylvia 是一个热爱学习的女孩子。 
  前段时间,Sylvia 参加了学校的军训。众所周知,军训的时候需要站方阵。 Sylvia所在的方阵中有n × m名学生,方阵的行数为 n,列数为m。 
  为了便于管理,教官在训练开始时,按照从前到后,从左到右的顺序给方阵中的学生从 1 到 n × m 编上了号码(参见后面的样例)。即:初始时,第 i 行第 j 列的学生的编号是(i − 1) × m + j。 
  然而在练习方阵的时候,经常会有学生因为各种各样的事情需要离队。在一天中,一共发生了 q 件这样的离队事件。每一次离队事件可以用数对(x, y) (1≤x≤n, 1≤y≤m)描述,表示第 x 行第 y 列的学生离队。 
  在有学生离队后,队伍中出现了一个空位。为了队伍的整齐,教官会依次下达这样的两条指令: 
    1. 向左看齐。这时第一列保持不动,所有学生向左填补空缺。不难发现在这条指令之后,空位在第 x 行第 m 列。 
    2. 向前看齐。这时第一行保持不动,所有学生向前填补空缺。不难发现在这条指令之后,空位在第 n 行第 m 列。 
  教官规定不能有两个或更多学生同时离队。即在前一个离队的学生归队之后,下一个学生才能离队。因此在每一个离队的学生要归队时,队伍中有且仅有第 n 行第 m 列一个空位,这时这个学生会自然地填补到这个位置。 
  因为站方阵真的很无聊,所以 Sylvia 想要计算每一次离队事件中,离队的同学的编号是多少。 
  注意:每一个同学的编号不会随着离队事件的发生而改变,在发生离队事件后方阵中同学的编号可能是乱序的。

输入共 q+1 行。 
第1 行包含 3 个用空格分隔的正整数 n, m, q,表示方阵大小是 n行 m 列,一共发生了q 次事件。 
接下来 q 行按照事件发生顺序描述了 q 件事件。每一行是两个整数 x, y,用一个空格分隔,表示这个离队事件中离队的学生当时排在第 x 行第 y 列。

按照事件输入的顺序,每一个事件输出一行一个整数,表示这个离队事件中离队学生的编号。

样例输入

2 2 3 1 1 2 2 1 2

样例输出

1 1 4

【样例解释】

[NOIP]2017列队——旋转treap/非旋转treap

列队的过程如上图所示,每一行描述了一个事件。 
在第一个事件中,编号为 1 的同学离队,这时空位在第一行第一列。接着所有同学向左标齐,这时编号为 2 的同学向左移动一步,空位移动到第一行第二列。然后所有同学向上标齐,这时编号为 4 的同学向上一步,这时空位移动到第二行第二列。最后编号为1 的同学返回填补到空位中。

【数据规模】

数据保证每一个事件满足 1≤x≤n,1≤y≤m。

[NOIP]2017列队——旋转treap/非旋转treap

这道题有许多做法,树状数组、线段树、平衡树都能做。这里只讲一下平衡树treap的做法。

通过题意我们会发现每次操作只改变第x行和最后一列。所以我们可以建n+1个平衡树(每行前m-1个数是一个,最后一列是一个),这样就可以快速查找并删除要操作的点并在最后一列插入删除的点。时间复杂度是O(Q*logN)级别,但看一下数据范围n*m的矩阵9*1010(比全世界人口都要多qwq),显然是存不下的。但只有3*105次查询,所以有许多点是不会被改动的,那么我们可以把这些点缩成一个点,然后记录一下每个点的左端和长度。每次操作时把操作的数所在点分成三个点(l~x-1;x;x+1~r)。所以最多只有2*q+2*n个点。每个平衡树在初始时只有一个点(最后一列的那个树是n个点)。注意非旋转treap分裂时并不是像常规一样分裂成两棵树,而是分裂成两个数和查询点三部分。

旋转treap

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int a,b;
int tot;
int n,m,q;
long long ans;
int w[3000010];
int ls[3000010];
int rs[3000010];
int root[300010];
int size[3000010];
long long l[3000010];
long long r[3000010];
void updata(int x)
{
size[x]=size[ls[x]]+size[rs[x]]+r[x]-l[x]+1;
}
void lturn(int &x)
{
if(!rs[x])
{
return ;
}
int t=rs[x];
rs[x]=ls[t];
ls[t]=x;
size[t]=size[x];
updata(x);
x=t;
}
void rturn(int &x)
{
if(!ls[x])
{
return ;
}
int t=ls[x];
ls[x]=rs[t];
rs[t]=x;
size[t]=size[x];
updata(x);
x=t;
}
void rotate(int &x)
{
if(w[rs[x]]<w[x])
{
lturn(x);
}
if(w[ls[x]]<w[x])
{
rturn(x);
}
}
void insert(int &x,long long L,long long R)
{
if(!x)
{
x=++tot;
l[x]=L;
r[x]=R;
w[x]=rand();
updata(x);
return ;
}
insert(rs[x],L,R);
updata(x);
rotate(x);
}
void del(int &x,int num)
{
if(!x)
{
return ;
}
if(x==num)
{
if(ls[x]*rs[x]==0)
{
x=ls[x]+rs[x];
}
else if(w[ls[x]]<w[rs[x]])
{
rturn(x);
}
else if(w[ls[x]]>=w[rs[x]])
{
lturn(x);
}
del(x,num);
return ;
}
if(ls[x]==num)
{
del(ls[x],num);
}
if(rs[x]==num)
{
del(rs[x],num);
}
updata(x);
}
void find1(int &x,int v)
{
if(!x)
{
return ;
}
if(size[ls[x]]>=v)
{
find1(ls[x],v);
updata(x);
rotate(x);
return ;
}
else if(size[x]-size[rs[x]]<v)
{
find1(rs[x],v-size[x]+size[rs[x]]);
updata(x);
rotate(x);
return ;
}
v-=size[ls[x]];
ans=l[x]+v-1;
if(v==1&&v==size[x]-size[rs[x]]-size[ls[x]])
{
del(x,x);
return ;
}
if(v==1)
{
l[x]++;
updata(x);
}
else if(v==size[x]-size[ls[x]]-size[rs[x]])
{
r[x]--;
updata(x);
}
else
{
int sum=++tot;
r[sum]=r[x];
r[x]=l[x]+v-2;
l[sum]=l[x]+v;
rs[sum]=rs[x];
rs[x]=sum;
updata(sum);
updata(x);
w[sum]=rand();
rotate(x);
}
}
void find2(int &x,int v)
{
if(!x)
{
return ;
}
if(size[ls[x]]>=v)
{
find2(ls[x],v);
updata(x);
rotate(x);
return ;
}
else if(size[x]-size[rs[x]]<v)
{
find2(rs[x],v-size[x]+size[rs[x]]);
updata(x);
rotate(x);
return ;
}
ans=l[x];
del(x,x);
}
int main()
{
srand(37975);
scanf("%d%d%d",&n,&m,&q);
for(long long i=1;i<=n;i++)
{
insert(root[i],(i-1)*m+1,(i-1)*m+m-1);
}
for(long long i=1;i<=n;i++)
{
insert(root[n+1],i*m,i*m);
}
while(q--)
{
scanf("%d%d",&a,&b);
if(b<m)
{
find1(root[a],b);
}
else
{
find2(root[n+1],a);
}
long long s=ans;
printf("%lld\n",s);
if(b<m)
{
find2(root[n+1],a);
long long t=ans;
insert(root[a],t,t);
}
insert(root[n+1],s,s);
}
return 0;
}

非旋转treap

#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<cstdio>
#include<vector>
#include<bitset>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
#define pr pair<int,ll>
using namespace std;
int rs[2000010];
int ls[2000010];
ll l[2000010];
ll r[2000010];
int size[2000010];
int num[2000010];
int root[500010];
int v[2000010];
int n,m,q;
int x,y;
int cnt;
int a,b,c,d;
pr now;
int build(ll L,ll R)
{
int rt=++cnt;
size[rt]=num[rt]=(int)(R-L+1);
l[rt]=L;
r[rt]=R;
v[rt]=rand();
return rt;
}
void pushup(int rt)
{
size[rt]=size[ls[rt]]+size[rs[rt]]+num[rt];
}
pr split(int rt,int k,int &x,int &y)
{
if(size[ls[rt]]>=k)
{
y=rt;
pr res=split(ls[rt],k,x,ls[y]);
pushup(rt);
return res;
}
else if(size[ls[rt]]+num[rt]>=k)
{
x=ls[rt];
y=rs[rt];
return make_pair(rt,1ll*(k-size[ls[rt]]+l[rt]-1));
}
else
{
x=rt;
pr res=split(rs[rt],k-size[ls[rt]]-num[rt],rs[x],y);
pushup(rt);
return res;
}
}
int merge(int x,int y)
{
if(!x||!y)
{
return x+y;
}
if(v[x]<v[y])
{
rs[x]=merge(rs[x],y);
pushup(x);
return x;
}
else
{
ls[y]=merge(x,ls[y]);
pushup(y);
return y;
}
}
int main()
{
srand(12378);
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n;i++)
{
root[i]=build(1ll*(i-1)*m+1,1ll*i*m-1);
}
root[n+1]=build(1ll*m,1ll*m);
for(int i=2;i<=n;i++)
{
root[n+1]=merge(root[n+1],build(1ll*i*m,1ll*i*m));
}
while(q--)
{
scanf("%d%d",&x,&y);
if(y==m)
{
now=split(root[n+1],x,a,b);
ls[now.first]=rs[now.first]=0;
size[now.first]=num[now.first];
printf("%lld\n",now.second);
root[n+1]=merge(a,merge(b,now.first));
}
else
{
now=split(root[x],y,a,b);
printf("%lld\n",now.second);
if(l[now.first]!=now.second)
{
cnt++;
l[cnt]=l[now.first];
r[cnt]=now.second-1;
size[cnt]=num[cnt]=(int)(r[cnt]-l[cnt]+1);
v[cnt]=rand();
a=merge(a,cnt);
}
if(r[now.first]!=now.second)
{
cnt++;
l[cnt]=now.second+1;
r[cnt]=r[now.first];
size[cnt]=num[cnt]=(int)(r[cnt]-l[cnt]+1);
v[cnt]=rand();
b=merge(cnt,b);
}
root[x]=merge(a,b);
l[now.first]=now.second;
r[now.first]=now.second;
num[now.first]=size[now.first]=1;
ls[now.first]=rs[now.first]=0;
root[n+1]=merge(root[n+1],now.first);
now=split(root[n+1],x,c,d);
ls[now.first]=rs[now.first]=0;
size[now.first]=num[now.first];
root[n+1]=merge(c,d);
root[x]=merge(root[x],now.first);
}
}
}