P3960
P3960 [NOIP2017 提高组] 列队
题意简述
给定一个 \(n\times m\) 的网格,按照从上到下、从左到右的顺序从1开始编号。有q次操作,每次操作删去第x行y列的格子,需要你输出这个格子的编号,然后这一行后面的格子往前平移填补空格,使得空位在第x行第m列。然后第m列的格子往上填补空缺,使得空位到达第n行第m列,删去的那个格子填到这个位置。
\(n,m,q\le 3\times 10^5\)
思路
首先来考虑线性的情况。
我们需要
- 删去一个位置上的数。
- 把这个数放到尾部。
对于2,使用一个 vector
即可。
对于1,不是真的把它删除。考虑用线段树维护整个序列的位置,每个节点记录这个区间被删除的数字个数 \(cnt\)。每次根据左儿子的size-cnt判断在左半边还是右半边,找到位置更新即可。
找到是第几个数,分类讨论:
- 在 \(1\sim m\),使用公式计算。
- 后面,在
vector
内查询。
这个线段树的长度需要达到 \(m+q\)。
进入正题
发现每一行是独立的,只跟最后一列有关。
我们用上面的方法维护每一行的线段树(称\(t_1\sim t_n\),对应 vector
\(v_1\sim v_n\)),不过区别是在 \(1\sim m-1\),因为 \(m\) 包括在最后一列构成的线段树中。(称 \(t\),对应 vector
\(v\))
讨论:
- \(y=m\),此时在 \(t\) 中查询第 \(x\) 个数,使用上面的分类讨论方法得到编号,然后插入到 \(v\)。
- \(y\neq m\),还是要像 \(1\) 一样,但是编号是要插入 \(v_x\)(第 \(m\) 列的现在变成 \(m-1\),而维护长度就是 \(m-1\),相当于插入了末尾了),在 \(t_x\) 中查询第 \(y\) 个数,得到的编号要插入 \(v\)(因为它到 \((n,m)\) 去了)。
线段树的大小:\(len=\max(n,m)+q\) 肯定够用。
由于开不下 \(n+1\) 个线段树,采用动态开点线段树。
写程序计算空间:\(len_{\max}=3\times 10^5+3\times 10^5=6\times 10^5\),写一个递归计算线段树长度为 \(6\times 10^5\) 时最大深度为 \(21\),考虑到一次最多涉及到 \(t_x,t\),故需要节点数为最大深度 \(\times\) 询问次数 \(\times2\), \(21\times 3\times 10^5\times 2\) 个节点。
注意动态开点线段树要用引用,有些地方开 long long
。