P3960 列队
Posted garen-wang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P3960 列队相关的知识,希望对你有一定的参考价值。
部分分分足够的题还要打正解就是折磨了哇!
先看一下这道D2T3的部分分:
(n leq 1000)的部分分,直接暴力模拟即可,因为(q leq 500),所以(O(nq))的模拟是可以过的。30pts到手。
同样(q leq 500),但是(n leq 300000)怎么办?离散化一下,继续模拟即可。50pts到手。
但是蒟蒻并看不懂这种离散化的操作,并没有成功地骗到分
听说还有一种(O(q^2))的做法,通过倒退来得到一个点的初始位置。我也没写出来。如果写得出来50pts就稳了。
(x=1)的部分分呢?
可以发现你需要维护两个序列,分别是第一行前((m-1))个元素和最后一列。
第一个序列要求你取出第(k)个数,然后从最后一列同样取出第(k)个数扔进第一个序列的最后,最先取出的那个数扔到最后一列的最后。
说白了,就是让你维护两个操作:删除第(k)个元素和在尾部插入。
哦。Splay水过。30pts到手。
满分做法
其实(x=1)的做法已经很提醒你正解就是这么搞的了。
我们对每一行的前((m-1))个元素建一个splay,单独对最后一列建一个splay。
每次修改,就是从里面取出元素,然后往另一个splay里面添加到尾部。
但是问题来了。(n leq 300000),不可能开几亿个结点啊!
其实,一些元素是不会分开的,我们直接把它们合并在一起。
实现方法就是让每一个结点代表一个区间,每次要取里面的元素就分裂这个节点即可。
因为(q leq 300000),所以并不会分裂太多,节点随便开多一点就够用了。
实现过程
其实难点就在于实现了。毕竟一个splay能让你调很久的。
况且这里是我见过的第一个带分裂节点的splay。
分裂节点的思想是这样的:
新建一个节点,给它指定一些参数。
然后把这个新节点插入,作为原节点的后继即可。
找后继就是很简单的操作,先进右儿子,然后拼命地进左儿子直到不能进为止,根据排序二叉树的性质,停下来的节点就是后继了。
还有一个重要的操作是删除节点。
我们之前是通过split的操作进行删除的,但是缺点是需要两个虚拟结点。
如果没有虚拟结点,我们要通过这样来进行操作:
把要删除的节点splay到根,看看她有没有左儿子。
如果没左儿子,那么它就是第一个节点,删除第一个节点的话,根节点直接给她右儿子即可。
否则就有点麻烦,你需要找到她的前驱,splay前驱到根,此时让前驱的右儿子直接等于待删除节点的右儿子!
同时再注意搞好那些pushup的操作。其实你可以没事多splay最底下的节点。
用splay写的缺点就是常数太大,不开O2过不了。
代码:(当然是借鉴@rqy dalao的题解)
// luogu-judger-enable-o2
#include<cstdio>
#define ll long long
const ll maxn = 3000005;
ll n, m, q;
ll size[maxn], fa[maxn], ch[maxn][2];
ll l[maxn], r[maxn];
ll tot;
struct Splay
{
ll root;
ll dir(ll x)
{
return ch[fa[x]][1] == x;
}
void connect(ll son, ll f, ll k)
{
fa[son] = f;
ch[f][k] = son;
}
void pushup(ll x)
{
size[x] = size[ch[x][0]] + size[ch[x][1]] + r[x] - l[x] + 1;
}
void rotate(ll x)
{
ll y = fa[x];
ll z = fa[y];
ll yk = dir(x);
ll zk = dir(y);
ll b = ch[x][yk ^ 1];
connect(b, y, yk);
connect(y, x, yk ^ 1);
connect(x, z, zk);
pushup(y);
pushup(x);
}
void splay(ll x, ll goal)
{
while(fa[x] != goal)
{
ll y = fa[x];
ll z = fa[y];
if(z != goal) dir(x) == dir(y) ? rotate(y) : rotate(x);
rotate(x);
}
if(goal == 0) root = x;
}
void insert(ll lx, ll rx)
{
ll now = root, f = 0;
while(now)
{
f = now; now = ch[now][1];
}
now = ++tot;
l[now] = lx; r[now] = rx; size[now] = rx - lx + 1;
fa[now] = f; if(f) ch[f][1] = now;
ch[now][0] = ch[now][1] = 0;
splay(now, 0);
}
ll splitnode(ll x, ll k)// source[l, l + k - 1], new[l + k, r]
{
ll 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;
splay(y, 0);
}
else
{
ll t = ch[x][1];
while(ch[t][0]) t = ch[t][0];
ch[t][0] = y; fa[y] = t;
splay(y, 0);
}
return y;
}
ll popkth(ll k)
{
ll now = root;
while(2333)
{
if(size[ch[now][0]] + r[now] - l[now] + 1 < k)
{
k -= size[ch[now][0]] + r[now] - l[now] + 1;
now = ch[now][1];
}
else if(size[ch[now][0]] >= k) now = ch[now][0];
else
{
k -= size[ch[now][0]];
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
{
ll t = ch[now][0];
while(ch[t][1]) t = ch[t][1];
splay(t, 0);
ch[t][1] = ch[now][1];
fa[ch[now][1]] = t;
pushup(root = t);
}
return l[now];
}
void print(int u)
{
if(ch[u][0]) print(ch[u][0]);
printf("[%lld, %lld] ", l[u], r[u]);
if(ch[u][1]) print(ch[u][1]);
}
} sp[300000];
ll read()
{
ll 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(ll i = 1; i <= n; i++)
{
sp[i].insert((i - 1) * m + 1, i * m - 1);
sp[0].insert(i * m, i * m);
}
while(q--)
{
ll x = read(), y = read();
if(y == m)
{
ll temp1 = sp[0].popkth(x);
sp[0].insert(temp1, temp1);
printf("%lld
", temp1);
}
else
{
ll temp1 = sp[x].popkth(y);
ll temp2 = sp[0].popkth(x);
sp[x].insert(temp2, temp2);
sp[0].insert(temp1, temp1);
printf("%lld
", temp1);
}
/*
for(ll i = 0; i <= n; i++)
{
sp[i].print(sp[i].root);
printf("
");
}
*/
}
return 0;
}
以上是关于P3960 列队的主要内容,如果未能解决你的问题,请参考以下文章