题意
一个 N ∗ M N*M N∗M的方阵。 Q Q Q次操作每次操作取出一个人,空出一个位置。在空格右边的人都向左移动一格,使得空格在行末,然后把下面几行的最后一格都向上移动,使得空格出现在最右下角。然后把这个取出的人放到最右下角。
求每次取出的人的编号。 ( N , M , Q ≤ 3 ∗ 1 0 5 ) (N,M,Q\le 3*10^5) (N,M,Q≤3∗105)
思路
1 Splay
首先想到的肯定是平衡树,因为每次操作只会影响到某一行和最后一列,操作又是在序列里delete和insert,这不就是平衡树该干的事情吗?
然后这题维护序列的思路和《方伯伯的OJ》又非常相似。在没有访问到的时候,把一个区间的点合并在同一个点上,要是需要用到其中的点,就先把序列split再做操作。
那么大体思路就成型了。用n个Splay维护每行的前m-1个位置,再用一个Splay维护最后一列。空间最多 O ( N ) O(N) O(N)(大概4倍常数),时间 O ( N l o g N ) O(NlogN) O(NlogN)(超大常数)。
2 线段树/树状数组
思路和上面一样,只不过更暴力了一点。
使用动态开点线段树实现上面Splay的操作。对每个点记录一个0/1表示这个点是否真实存在于当前的序列中,查找第k个用线段树上二分,删除只要单点修改把1改成0就行,插入直接在序列末插入,空间开 O ( N + Q ) O(N+Q) O(N+Q)。
所以最后复杂度仍旧是 O ( N l o g N ) O(NlogN) O(NlogN),但是常数小了很多。
线段树的这种操作还是应该学一学啊。毕竟Splay调了一个早上。
代码
Splay
#include<bits/stdc++.h>
using namespace std;
#define rdi read<int>
#define rdl read<long long>
template<typename T> inline T read(){
T x = 0, fh = 1;
char c = getchar();
while (c < '0' || c > '9'){if (c == '-') fh = -1; c = getchar();}
while (c >= '0' && c <= '9') x = (x<<3)+(x<<1)+c-'0', c = getchar();
return x*fh;
}
typedef long long LL;
const int N = 2e6 + 10;
int n, m, q;
int cnt, root[N], fa[N], ch[N][2], len[N], sum[N];
LL val[N];
inline void push_up(int u)
{
sum[u] = sum[ch[u][0]] + sum[ch[u][1]] + len[u];
}
inline int son(int u){return ch[fa[u]][1] == u;}
inline void rotate(int u)
{
int v = fa[u], w = fa[v];
int ws = son(v), vs = son(u);
ch[w][ws] = u;
fa[v] = u;
ch[v][vs] = ch[u][vs^1];
fa[ch[u][vs^1]] = v;
ch[u][vs^1] = v;
fa[u] = w;
push_up(v); push_up(u);
}
inline void splay(int u, int &goal)
{
int rt = fa[goal];
while (fa[u] != rt){
if (fa[fa[u]] != rt) rotate(son(u) == son(fa[u]) ? fa[u] : u);
rotate(u);
}
if (rt == 0) goal = u;
}
inline int newnode(LL _val, int _len)
{
val[++cnt] = _val;
sum[cnt] = len[cnt] = _len;
return cnt;
}
void init()
{
if (m > 1)
for (int i = 1; i <= n; ++ i)
root[i] = newnode(1LL * (i - 1) * m + 1, m - 1);
root[0] = newnode(m, 1);
for (int i = 2; i <= n; ++ i){
ch[root[0]][1] = newnode(1LL * i * m, 1);
fa[ch[root[0]][1]] = root[0];
splay(ch[root[0]][1], root[0]);
}
}
LL Find(int rk, int &rt)
{
if (rk > sum[rt]) rk = sum[rt];
if (rk == 0) rk = 1;
int u = rt;
while (u){
if (rk <= sum[ch[u][0]]){u = ch[u][0]; continue;}
rk -= sum[ch[u][0]];
if (rk <= len[u]) break;
rk -= len[u];
u = ch[u][1];
}
splay(u, rt);
return val[u] + rk - 1;
}
void delet(int rk, int u, int &rt)
{
rk -= sum[ch[u][0]];
if ((rk == 1 || rk == len[u]) && len[u] != 1){
len[u]--; sum[u]--;
if (rk == 1) val[u]++;
}
else{
if (len[u] == 1){
if (ch[u][0] == 0 && ch[u][1] == 0) rt = 0;
else if (ch[u][0] == 0){rt = ch[u][1]; fa[ch[u][1]] = 0;}
else if (ch[u][1] == 0){rt = ch[u][0]; fa[ch[u][0]] = 0;}
else{
Find(sum[ch[u][0]], rt);
Find(len[u]+1, ch[rt][1]);
ch[fa[u]][0] = 0;
splay(fa[u], rt);
}
}
else{
Find(sum[ch[u][0]], rt);
Find(len[u]+1, u == rt ? rt : ch[rt][1]);
ch[u][1] = newnode(val[u] + rk, len[u] - rk);
fa[ch[u][1]] = u;
len[u] = rk - 1;
splay(ch[u][1], rt);
}
}
}
void insert(int u, int rk, int &rt)
{
if (rt == 0){rt = u; return;}
Find(rk, rt);
ch[rt][1] = u;
fa[u] = rt;
splay(u, rt);
}
int main()
{
n = rdi(); m = rdi(); q = rdi();
init();
for (int i = 1; i <= q; ++ i){
int x, y; x = rdi(); y = rdi();
if (y < m){
LL now = Find(y, root[x]);
printf("%lld\n", now);
Find(x, root[0]);
int del = root[0];
delet(y, root[x], root[x]);
delet(x, root[0], root[0]);
ch[del][0] = ch[del][1] = fa[del] = 0;
insert(del, m-2, root[x]);
insert(newnode(now, 1), n-1, root[0]);
}
else{
LL now = Find(x, root[0]);
printf("%lld\n", now);
delet(x, root[0], root[0]);
insert(newnode(now, 1), n-1, root[0]);
}
}
return 0;
}