[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

【样例解释】

 

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

【数据规模】

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

 

 

这道题有许多做法,树状数组、线段树、平衡树都能做。这里只讲一下平衡树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);
        }
    }
}
posted @ 2018-04-17 10:34  The_Virtuoso  阅读(586)  评论(0编辑  收藏  举报