洛谷 P3960 列队 解题报告

P3960 列队

题目描述

\(Sylvia\)是一个热爱学习的女♂孩子。

前段时间,\(Sylvia\)参加了学校的军训。众所周知,军训的时候需要站方阵。

\(Sylvia\)所在的方阵中有\(n \times m\)名学生,方阵的行数为\(n\),列数为\(m\)

为了便于管理,教官在训练开始时,按照从前到后,从左到右的顺序给方阵中 的学生从\(1\)\(n \times m\)编上了号码(参见后面的样例)。即:初始时,第\(i\)行第\(j\)列 的学生的编号是\((i-1)\times m + j\)

然而在练习方阵的时候,经常会有学生因为各种各样的事情需要离队。在一天 中,一共发生了\(q\)件这样的离队事件。每一次离队事件可以用数对\((x,y) (1 \le x \le n, 1 \le y \le m)\)描述,表示第\(x\)行第\(y\)列的学生离队。

在有学生离队后,队伍中出现了一个空位。为了队伍的整齐,教官会依次下达 这样的两条指令:

向左看齐。这时第一列保持不动,所有学生向左填补空缺。不难发现在这条 指令之后,空位在第\(x\)行第\(m\)列。

向前看齐。这时第一行保持不动,所有学生向前填补空缺。不难发现在这条 指令之后,空位在第\(n\)行第\(m\)列。

教官规定不能有两个或更多学生同时离队。即在前一个离队的学生归队之后, 下一个学生才能离队。因此在每一个离队的学生要归队时,队伍中有且仅有第\(n\)行 第\(m\)列一个空位,这时这个学生会自然地填补到这个位置。

因为站方阵真的很无聊,所以\(Sylvia\)想要计算每一次离队事件中,离队的同学 的编号是多少。

注意:每一个同学的编号不会随着离队事件的发生而改变,在发生离队事件后 方阵中同学的编号可能是乱序的。

输入输出格式

输入格式:

输入共 \(q+1\) 行。

第 1 行包含 3 个用空格分隔的正整数\(n, m, q\),表示方阵大小是\(n\)\(m\)列,一共发 生了\(q\)次事件。

接下来\(q\)行按照事件发生顺序描述了\(q\)件事件。每一行是两个整数\(x,y\),用一个空格分隔,表示这个离队事件中离队的学生当时排在第\(x\)行第\(y\)列。

输出格式:

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


思路:
用平衡树点分裂的写法。

我这里采用的是\(splay\),维护左右区间\(L,R\),子树大小\(siz\),两个儿子和父亲。

每一行维护一颗,最后一列维护一颗

查询时要分裂就分裂,放到最后一列,再把最后一列的放过来

我犯的细节问题(调+拍了好久):

  1. 把节点插在最右边也要旋转!!!因为是双旋所以对树的深度有平衡作用
  2. 分裂时传人区间第几个时注意减去左子树的大小

Code:

#include <cstdio>
#include <cstring>
#define ls ch[now][0]
#define rs ch[now][1]
#define f par[now]
#define ll long long
const ll N=2500010;
ll ch[N][2],par[N],siz[N],L[N],R[N];
ll root[N],n,m,q,tot,out;
ll identity(ll now)
{
    return ch[f][1]==now;
}
void connect(ll fa,ll now,ll typ)
{
    f=fa;
    ch[fa][typ]=now;
}
void updata(ll now)
{
    siz[now]=siz[ls]+siz[rs]+R[now]+1-L[now];
}
void Rotate(ll now)
{
    ll p=f,typ=identity(now);
    connect(p,ch[now][typ^1],typ);
    connect(par[p],now,identity(p));
    connect(now,p,typ^1);
    updata(p);updata(now);
}
void splay(ll now,ll to,ll id)
{
    to=par[to];
    for(;f!=to;Rotate(now))
        if(par[f]!=to)
            Rotate(identity(now)^identity(f)?now:f);
    if(!to) root[id]=now;
}
ll New(ll l,ll r)
{
    siz[++tot]=r+1-l;L[tot]=l;R[tot]=r;
    return tot;
}
ll split(ll now,ll pos)//返回分裂出的那个点
{
    siz[now]--;
    if(out) printf("%lld\n",L[now]+pos-1);
    if(pos==1)
    {
        L[now]++;
        return New(L[now]-1,L[now]-1);
    }
    if(pos==R[now]+1-L[now])
    {
        R[now]--;
        return New(R[now]+1,R[now]+1);
    }
    ll Ls=ls;
    connect(now,New(L[now],L[now]+pos-2),0);
    connect(ls,Ls,0);
    updata(ls);
    L[now]=L[now]+pos;
    updata(now);
    return New(L[now]-1,L[now]-1);
}
ll get_right(ll now,ll is)
{
    if(is) siz[now]++;
    return rs?get_right(rs,is):now;
}
ll Find(ll id,ll pos)//返回要拿走的点
{
    ll now=root[id];
    while(233)
    {
        if(siz[ls]>=pos) now=ls;
        else if(siz[now]-siz[rs]<pos) pos-=siz[now]-siz[rs],now=rs;
        else
        {
            pos-=siz[ls];
            splay(now,root[id],id);
            if(L[now]==R[now])
            {
                if(out) printf("%lld\n",L[now]);
                if(ls)
                {
                    ll rt=get_right(ls,0);
                    splay(rt,ls,id);
                    connect(root[id]=rt,rs,1);
                    updata(rt);
                }
                else root[id]=rs;
                par[root[id]]=0;
                ls=rs=0,siz[now]=1;
                return now;
            }
            return split(now,pos);
        }
    }
}
void put(ll num,ll id)//把啥子编号的点放在某棵树最右边
{
    if(!root[id]) root[id]=num;
    else
    {
        connect(get_right(root[id],1),num,1);
        splay(num,root[id],id);
    }
}
void Operator(ll line,ll row)
{
    if(row!=m)
    {
        out=1;put(Find(line,row),n+1);
        out=0;put(Find(n+1,line),line);
    }
    else
        out=1,put(Find(n+1,line),n+1);
}
ll build(ll l,ll r)
{
    if(l>r) return 0;
    if(l==r) return New(l*m,l*m);
    ll mid=l+r>>1;
    ll now=New(mid*m,mid*m);
    ls=build(l,mid-1);
    par[ls]=now;
    rs=build(mid+1,r);
    par[rs]=now;
    updata(now);
    return now;
}
void init()
{
    scanf("%lld%lld%lld",&n,&m,&q);
    for(ll i=1;i<=n;i++)
        root[i]=New(m*(i-1)+1,m*i-1);
    root[n+1]=build(1,n);
}
void work()
{
    ll x,y;
    for(ll i=1;i<=q;i++)
    {
        scanf("%lld%lld",&x,&y);
        Operator(x,y);
    }
}
int main()
{
    //freopen("data.in","r",stdin);
    //freopen("wr.out","w",stdout);
    init();
    work();
    return 0;
}

2018.7.27

posted @ 2018-07-27 20:34  露迭月  阅读(272)  评论(0编辑  收藏  举报