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 列队的主要内容,如果未能解决你的问题,请参考以下文章

Luogu P3960 列队(动态开点线段树)

P3960 列队

P3960 列队

[NOIP2017]列队

[luogu P3960] [noip2017 d2t3] 队列

并发系统数据细节-列队