P3960 列队

Posted garen-wang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P3960 列队相关的知识,希望对你有一定的参考价值。

终于把去年完全不懂的题做出来了!激动!


这道题可以说是很经典了。即使去年的题,还是经典!

题目叫你维护一个矩阵,每次操作支持删除掉第(x)行第(y)列的数字,然后自动执行两个动作:

  1. 向左看齐。每个同学如果左边有空位的话就向左移动一位。显然这个操作过后空位会在第(x)行第(m)列。

  2. 向前看齐。每个同学如果前面有空位的话就向前移动一位。显然这个操作过后空位会在第(n)行第(m)列。

每次操作后都要输出一个数字,就是被删除的点的编号。


首先当然是暴力模拟走一波。先让数组暴力对齐,然后我们才能想到优化。

发现从第(x)行的某一列删除指定的第(y)个点的话,第一个操作只会在第(x)行中出现!特殊地,如果恰好是第(m)个点的话,无需执行操作。

第一个操作过后,当空位在第(x)行时,发现第二个操作只会在第(m)列中出现!如果恰好是第(n)行的话,无需执行操作。

显然我们需要找出某一行中的第(k)大,然后删除这个点。用什么数据结构?

链表?删除点是很方便,但是找第(k)大显然会超时。

于是我们想到了平衡树。。。

对于元素向前挪向左挪的操作,其实我们只需要使用平衡树,这些操作就不存在了。

存下 每一行除了最后一个 和 最后一列 的元素。最后一列的比较特殊,因为也需要挪。

显然,我们找出第(k)大然后记录一下编号再删除,同时从最后一列中找出第(x)个,把这个点移动到行的末尾,新点就直接加入到最后一列的末尾。

算法终于介绍完了。

但是:(n, m leq 3 ime 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删除操作:

  1. 将待删除的点splay到根,将他左右儿子到它的父亲标记去掉。

  2. 如果根的左儿子为空,则新的根结点就是他的右子树。

  3. 如果根的儿子不为空,那么找到他的前驱,将他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
", 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("
");
    }
    s[0].print(s[0].root);
    printf("
");
    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
", pop2);
    }
    return 0;
}

以上是关于P3960 列队的主要内容,如果未能解决你的问题,请参考以下文章

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

P3960 列队

P3960 列队

[NOIP2017]列队

[luogu P3960] [noip2017 d2t3] 队列

并发系统数据细节-列队