P3960 列队
终于把去年完全不懂的题做出来了!激动!
这道题可以说是很经典了。即使去年的题,还是经典!
题目叫你维护一个矩阵,每次操作支持删除掉第\(x\)行第\(y\)列的数字,然后自动执行两个动作:
-
向左看齐。每个同学如果左边有空位的话就向左移动一位。显然这个操作过后空位会在第\(x\)行第\(m\)列。
-
向前看齐。每个同学如果前面有空位的话就向前移动一位。显然这个操作过后空位会在第\(n\)行第\(m\)列。
每次操作后都要输出一个数字,就是被删除的点的编号。
首先当然是暴力模拟走一波。先让数组暴力对齐,然后我们才能想到优化。
发现从第\(x\)行的某一列删除指定的第\(y\)个点的话,第一个操作只会在第\(x\)行中出现!特殊地,如果恰好是第\(m\)个点的话,无需执行操作。
第一个操作过后,当空位在第\(x\)行时,发现第二个操作只会在第\(m\)列中出现!如果恰好是第\(n\)行的话,无需执行操作。
显然我们需要找出某一行中的第\(k\)大,然后删除这个点。用什么数据结构?
链表?删除点是很方便,但是找第\(k\)大显然会超时。
于是我们想到了平衡树。。。
对于元素向前挪向左挪的操作,其实我们只需要使用平衡树,这些操作就不存在了。
存下 每一行除了最后一个 和 最后一列 的元素。最后一列的比较特殊,因为也需要挪。
显然,我们找出第\(k\)大然后记录一下编号再删除,同时从最后一列中找出第\(x\)个,把这个点移动到行的末尾,新点就直接加入到最后一列的末尾。
算法终于介绍完了。
但是:\(n, m \leq 3 \time 10^5\),数据巨大!
解决方法是:一开始能合并的节点就合并,需要用到某个节点就从那个节点中分裂!
请理解这个splitnode函数。
int splitnode(int x, int k)// source[l, l + k - 1], new [l + k, r]
{
int y = ++tot;
l[y] = l[x] + k, r[y] = r[x];
r[x] = l[x] + k - 1;
if(ch[x][1] == 0)
{
ch[x][1] = y; fa[y] = x;
}
else
{
int t = ch[x][1];
while(ch[t][0]) t = ch[t][0];
ch[t][0] = y; fa[y] = t;
//while(t != x) pushup(t), t = fa[t];
}
splay(y, 0);
return y;
}
然后如何删除点?
在splay里面,我们常用的删除点的方法是找他的前驱后继,split出这个区间,然后把它删除掉。
split操作的缺点是你需要两个虚拟节点!
但是显然在这道题中,不可以有虚拟节点(至少我不会写),所以这种方法去掉。
现在给出无split的splay删除操作:
-
将待删除的点splay到根,将他左右儿子到它的父亲标记去掉。
-
如果根的左儿子为空,则新的根结点就是他的右子树。
-
如果根的儿子不为空,那么找到他的前驱,将他splay到根位置,他的右儿子是原根结点的右儿子,父亲标记也做一下,最后pushup即可。
时间关系写不了了,代码:
#include<cstdio>
const int maxn = 3000005;
long long l[maxn], r[maxn], size[maxn], fa[maxn], ch[maxn][2];
int tot;
long long n, m, q;
struct Splay
{
int root;
int identify(int x)
{
return ch[fa[x]][1] == x;
}
void connect(int son, int f, int k)
{
fa[son] = f;
ch[f][k] = son;
}
void pushup(int x)
{
size[x] = size[ch[x][0]] + size[ch[x][1]] + (r[x] - l[x] + 1);
}
void rotate(int x)
{
int y = fa[x];
int z = fa[y];
int yk = identify(x);
int zk = identify(y);
int b = ch[x][yk ^ 1];
connect(b, y, yk);
connect(y, x, yk ^ 1);
connect(x, z, zk);
pushup(y);
pushup(x);
}
void splay(int x, int goal)
{
while(fa[x] != goal)
{
int y = fa[x];
int z = fa[y];
if(z != goal) identify(x) == identify(y) ? rotate(y) : rotate(x);
rotate(x);
}
if(goal == 0) root = x;
}
void insert(long long ll, long long rr)
{
int now = root, f = 0;
while(now)
{
f = now;
now = ch[now][1];
}
now = ++tot;
l[now] = ll, r[now] = rr;
fa[now] = f; if(f) ch[f][1] = now;
splay(now, 0);
}
int splitnode(int x, int k)// source[l, l + k - 1], new [l + k, r]
{
int y = ++tot;
l[y] = l[x] + k, r[y] = r[x];
r[x] = l[x] + k - 1;
if(ch[x][1] == 0)
{
ch[x][1] = y; fa[y] = x;
}
else
{
int t = ch[x][1];
while(ch[t][0]) t = ch[t][0];
ch[t][0] = y; fa[y] = t;
//while(t != x) pushup(t), t = fa[t];
}
splay(y, 0);
return y;
}
long long popkth(int k)
{
int now = root;
while(2333)
{
int left = ch[now][0];
if(size[left] + (r[now] - l[now] + 1) < k)
{
k -= size[left] + (r[now] - l[now] + 1);
now = ch[now][1];
}
else if(size[left] >= k) now = left;
else
{
k -= size[left];
if(k != (r[now] - l[now] + 1)) splitnode(now, k);
if(k != 1) now = splitnode(now, k - 1);
break;
}
}
splay(now, 0);
fa[ch[now][0]] = fa[ch[now][1]] = 0;
if(ch[now][0] == 0) root = ch[now][1];
else
{
int t = ch[now][0];
while(ch[t][1]) t = ch[t][1];
splay(t, 0);
ch[t][1] = ch[now][1];
fa[ch[t][1]] = t;
pushup(t);
}
return l[now];
}
void print(int u)
{
if(ch[u][0]) print(ch[u][0]);
printf("%lld %lld\n", l[u], r[u]);
if(ch[u][1]) print(ch[u][1]);
}
} s[300005];
long long read()
{
long long ans = 0, s = 1;
char ch = getchar();
while(ch > '9' || ch < '0')
{
if(ch == '-') s = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
ans = ans * 10 + ch - '0';
ch = getchar();
}
return s * ans;
}
int main()
{
n = read(), m = read(), q = read();
for(long long i = 1; i <= n; i++)
{
s[i].insert((i - 1) * m + 1, i * m - 1);
s[0].insert(i * m, i * m);
}
/*
for(int i = 1; i <= n; i++)
{
s[i].print(s[i].root);
printf("\n");
}
s[0].print(s[0].root);
printf("\n");
return 0;
*/
while(q--)
{
int x = read(), y = read();
long long pop1 = s[0].popkth(x);
s[x].insert(pop1, pop1);
long long pop2 = s[x].popkth(y);
s[0].insert(pop2, pop2);
printf("%lld\n", pop2);
}
return 0;
}