P8496 [NOI2022] 众数

1 P8496 [NOI2022] 众数

2 题目描述

对于一个序列,定义其众数为序列中出现次数严格大于一半的数字。注意该定义与一般的定义有出入,在本题中请以题面中给出的定义为准。

一开始给定 \(n\) 个长度不一的正整数序列,编号为 \(1 \sim n\),初始序列可以为空。这 \(n\) 个序列被视为存在,其他编号对应的序列视为不存在。

\(q\) 次操作,操作有以下类型:

  • \(1 \ x \ y\):在 \(x\) 号序列末尾插入数字 \(y\)。保证 \(x\) 号序列存在,且 \(1 \le x, y \le n + q\)
  • \(2 \ x\):删除 \(x\) 号序列末尾的数字,保证 \(x\) 号序列存在、非空,且 \(1 \le x \le n + q\)
  • \(3 \ m \ x_1 \ x_2 \ x_m\):将 \(x_1, x_2, \ldots, x_m\) 号序列顺次拼接,得到一个新序列,并询问其众数。如果不存在满足上述条件的数,则返回 \(-1\)。数据保证对于任意 \(1 \le i \le m\)\(x_i\) 是一个仍然存在的序列,\(1 \le x_i \le n + q\),且拼接得到的序列非空。注意:不保证 \(\boldsymbol{x_1, \ldots, x_m}\) 互不相同,询问中的合并操作不会对后续操作产生影响。
  • \(4 \ x_1 \ x_2 \ x_3\):新建一个编号为 \(x_3\) 的序列,其为 \(x_1\) 号序列后顺次添加 \(x_2\) 号序列中数字得到的结果,然后删除 \(x_1, x_2\) 对应的序列。此时序列 \(x_3\) 视为存在,而序列 \(x_1, x_2\) 被视为不存在,在后续操作中也不会被再次使用。保证 \(1 \le x_1, x_2, x_3 \le n + q\)\(x_1 \ne x_2\)、序列 \(x_1, x_2\) 在操作前存在、且在操作前没有序列使用过编号 \(x_3\)

输入格式

输入的第一行包含两个正整数 \(n\)\(q\),分别表示数列的个数和操作的次数,保证 \(n \le 5 \times {10}^5\)\(q \le 5 \times {10}^5\)

接下来 \(n\) 行,第 \(i\) 行表示编号为 \(i\) 的数列。每一行的第一个非负整数 \(l_i\) 表示初始第 \(i\) 号序列的数字个数,接下来有 \(l_i\) 个非负整数 \(a_{i,j}\) 按顺序表示数列中的数字。假定 \(C_l = \sum l_i\) 代表输入序列长度之和,则保证 \(C_l \le 5 \times {10}^5\)\(a_{i,j} \le n + q\)

接下来 \(q\) 行,每行若干个正整数,表示一个操作,并按照题面描述中的格式输入。

假定 \(C_m = \sum m\) 代表所有操作 \(3\) 需要拼接的序列个数之和,则保证 \(C_m \le 5 \times {10}^5\)

输出格式

对于每次询问,一行输出一个整数表示对应的答案。

样例 #1

样例输入 #1

2 8
3 1 1 2
3 3 3 3
3 1 1
3 1 2
4 2 1 3
3 1 3
2 3
3 1 3
1 3 1
3 1 3

样例输出 #1

1
3
-1
3
-1

样例 #2

样例输入 #2

4 9
1 1
1 2
1 3
1 4
3 4 1 2 3 4
1 1 2
3 2 1 2
2 3
3 3 1 2 3
1 4 4
1 4 4
1 4 4
3 4 1 2 3 4

样例输出 #2

-1
2
2
4

提示

【样例解释 #1】

第一次询问查询序列 \(1\) 的众数。由于序列包含两个 \(1\),超过序列长度的一半,因此众数为 \(1\)

第二次询问查询序列 \(2\) 的众数。由于序列只包含 \(3\),因此众数为 \(3\)

第三次询问询问序列 \(3\) 的众数。此时序列 \(3\)\((3, 3, 3, 1, 1, 2)\),不存在出现次数大于 \(3\) 次的数,因此输出为 \(-1\)


【样例解释 #2】

第一次询问查询序列 \(1, 2, 3, 4\) 拼接后得到的序列的众数。拼接的结果为 \((1, 2, 3, 4)\),不存在出现次数大于两次的数,因此输出为 \(-1\)

第四次询问查询序列 \(1, 2, 3, 4\) 拼接后得到的序列的众数。拼接的结果为 \((1, 2, 2, 4, 4, 4, 4)\),众数为 \(4\)

【数据范围】

对于所有测试数据,保证 \(1 \le n, q, C_m, C_l \le 5 \times {10}^5\)

3 题解

首先不考虑 \(2\) 操作。容易发现,此时序列内数的相对顺序并不重要,可以看作若干集合。

考虑 \(1, 4\) 操作,我们用线段树合并维护这 \(n + q\) 个集合即可,由于合并时 \(x_1 \ne x_2\),故复杂度有保障。

如何求解一个集合的绝对众数呢?

容易想到在线段树上每个权值处维护出现次数,然后直接全局最大值即可求出众数,判断是否大于总大小一半即可。

这个做法显然不能扩展到多个集合一起求众数的情况。

考虑性质:如果在线段树上某一区间所有值的出现次数之和小于等于 \(\lfloor \dfrac{n}{2} \rfloor\),那么这个区间的所有值的出现次数最大值也一定小于等于 \(\lfloor \dfrac{n}{2} \rfloor\)

因此,我们维护线段树上数值出现次数的和,查询时在 \(m\) 棵线段树上一起二分:

如果 \(m\) 棵线段树的左区间的和大于 \(\lfloor \dfrac{\sum n}{2} \rfloor\),那么右区间的和一定小于等于 \(\lfloor \dfrac{\sum n}{2} \rfloor\),故直接向左区间走。

否则就判断 \(m\) 棵线段树的右区间的和是否大于 \(\lfloor \dfrac{\sum n}{2} \rfloor\),如果是则向右区间走,否则直接返回 \(-1\)

考虑 \(2\) 操作,对每个序列维护一个链表,\(1\) 操作是在末端插入,\(2\) 操作是在末端删除,\(4\) 操作是将 \(x_1\) 的末端与 \(x_2\) 的前端连接。

复杂度 \(O(\sum m \log V)\)

4 代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <cstdlib>
#include <cmath>
#include <deque>
using namespace std;
#define pii pair<int,int>
#define mp make_pair
const int N = 1e6 + 10;
#define int long long
typedef long long ll;
int read()
{
    int x = 0, f = 1;
    char c = getchar();
    while (c < '0' || c > '9')
    {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9')
    {
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }
    return x * f;
}
int n, q, cnt, tot;
int a[N], rt[N];
ll siz[N];
int head[N << 1], nxt[N << 1], tail[N << 1], h[N << 1], pre[N << 1];
struct sgt
{
    int tot;
    struct node
    {
        int ls, rs;
        ll sum;
    }t[N * 37];
    int build()
    {
        tot++;
        t[tot].ls = t[tot].rs = t[tot].sum = 0;
        return tot;
    }
    void pushup(int p) {t[p].sum = t[t[p].ls].sum + t[t[p].rs].sum;}
    void modify(int p, int l, int r, int pos, ll d)
    {
        if (l == r)
        {
            t[p].sum += d;
            return ;
        }
        int mid = (l + r) >> 1;
        if (pos <= mid)
        {
            if (!t[p].ls) t[p].ls = build();
            modify(t[p].ls, l, mid, pos, d);
        }
        else
        {
            if (!t[p].rs) t[p].rs = build();
            modify(t[p].rs, mid + 1, r, pos, d);
        }
        pushup(p);
    }
    int merge(int p, int q, int l, int r)
    {
        if (!p || !q) return p + q;
        if (l == r)
        {
            t[p].sum += t[q].sum;
            return p;
        }
        int mid = (l + r) >> 1;
        t[p].ls = merge(t[p].ls, t[q].ls, l, mid);
        t[p].rs = merge(t[p].rs, t[q].rs, mid + 1, r);
        pushup(p);
        return p;
    }
    ll S() {ll res = 0; for (int i = 1; i <= cnt; i++) res += t[t[a[i]].ls].sum; return res;}
    ll S2() {ll res = 0; for (int i = 1; i <= cnt; i++) res += t[t[a[i]].rs].sum; return res;}
    int query(int l, int r, int d)
    {
        if (l == r) return l;
        int mid = (l + r) >> 1;
        if (S() > d / 2)
        {
            for (int i = 1; i <= cnt; i++) a[i] = t[a[i]].ls;
            return query(l, mid, d);
        }
        if (S2() > d / 2)
        {
            for (int i = 1; i <= cnt; i++) a[i] = t[a[i]].rs;
            return query(mid + 1, r, d);
        }
        return -1;
    }
}T;
signed main()
{
    n = read(), q = read();
    for (int i = 1; i <= n; i++)
    {
        siz[i] = read(); rt[i] = T.build();
        for (int j = 1; j <= siz[i]; j++)
        {
            int x = read();
            if (!head[i]) head[i] = ++tot, tail[i] = tot, h[tot] = x;
            else
            {
                nxt[tail[i]] = ++tot;
                pre[tot] = tail[i];
                tail[i] = tot;
                h[tot] = x;
            }
            T.modify(rt[i], 0, n + q, x, 1);
        }
    }
    for (int i = 1; i <= q; i++)
    {
        int opt = read();
        if (opt == 1)
        {
            int x = read(), y = read(); siz[x]++;
            if (!head[x]) head[x] = ++tot, tail[x] = tot, h[tot] = y;
            else
            {
                nxt[tail[x]] = ++tot;
                pre[tot] = tail[x];
                tail[x] = tot;
                h[tot] = y;
            }
            T.modify(rt[x], 0, n + q, y, 1);
        }
        if (opt == 2)
        {
            int x = read(); siz[x]--;
            T.modify(rt[x], 0, n + q, h[tail[x]], -1);
            if (!siz[x]) head[x] = tail[x] = 0;
            else
            {
                tail[x] = pre[tail[x]];
                nxt[tail[x]] = 0;
            }
        }
        if (opt == 3)
        {
            int m = read();
            ll sum = 0;
            cnt = 0;
            for (int j = 1; j <= m; j++)
            {
                int x = read();
                a[++cnt] = rt[x];
                sum += (ll)siz[x];
            }
            printf("%lld\n", T.query(0, n + q, sum));
        }
        if (opt == 4)
        {
            int x = read(), y = read(), z = read();
            if (!siz[x]) head[z] = head[y], tail[z] = tail[y];
            else if (!siz[y]) head[z] = head[x], tail[z] = tail[x];
            else 
            {
                head[z] = head[x]; tail[z] = tail[y];
                nxt[tail[x]] = head[y]; pre[head[y]] = tail[x];
            }
            rt[z] = T.merge(rt[x], rt[y], 0, n + q);
            siz[z] = siz[x] + siz[y];
        }
    }
    return 0;
}

欢迎关注

posted @ 2022-10-08 15:34  David24  阅读(41)  评论(0编辑  收藏  举报