[HNOI2009] 梦幻布丁

[HNOI2009] 梦幻布丁

题目描述

n 个布丁摆成一行,进行 m 次操作。每次将某个颜色的布丁全部变成另一种颜色的,然后再询问当前一共有多少段颜色。

例如,颜色分别为 1,2,2,1 的四个布丁一共有 3 段颜色.

输入格式

第一行是两个整数,分别表示布丁个数 n 和操作次数 m。  
第二行有 n 个整数,第 i 个整数表示第 i 个布丁的颜色 ai。  
接下来 m 行,每行描述一次操作。每行首先有一个整数 op 表示操作类型:

  • op=1,则后有两个整数 x,y,表示将颜色 x 的布丁全部变成颜色 y
  • op=2,则表示一次询问。

输出格式

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

样例 #1

样例输入 #1

4 3
1 2 2 1
2
1 2 1
2

样例输出 #1

3
1

提示

样例 1 解释

初始时布丁颜色依次为 1,2,2,1,三段颜色分别为 [1,1],[2,3],[4,4]。  
一次操作后,布丁的颜色变为 1,1,1,1,只有 [1,4] 一段颜色。

数据规模与约定

对于全部的测试点,保证 1n,m1051ai,x,y106

提示

请注意,不保证颜色的编号不大于 n,也不保证 xym 不是颜色的编号上限。

 

解题思路

  容易想到的暴力做法是,用 std::vector 维护各个颜色的下标有那些,对于询问的 xy,把颜色是 x 的下标 i 都改成 ai=y,并将这些下标都存到颜色是 y 的 std::vector 中,同时清空颜色是 x 的 std::vector,最后枚举数组 a 统计颜色段即可。

  可以发现随着询问的增加,答案会逐渐变小,这是因为每次将颜色 x 修改成 y,颜色 y 的下标会增多,颜色 x 的下标清零,意味着颜色段只可能变少,不会变多。为此我们只关心当颜色是 x 的下标 i 变成颜色 y 时,是否会减少颜色段,即如果 ai1=yai+1=y 时,颜色段就会减少。

  这时就可以用启发式合并了,启发式合并的概念就是如果要合并两个集合的元素,那么应该把元素少的集合合并到元素多的集合,这样可以保证合并次数为 O(nlogn) 的级别。考虑每个元素对合并次数的贡献是多少,由于该元素只会合并到元素数量比它大的集合中,因此合并后该元素所在集合的大小至少为原来集合的两倍,假设一共有 n 个元素,那么这个元素最多能合并 O(logn) 次,因此所有元素的合并次数即总共的合并次数就是 O(nlogn) 级别。

  在这题中我们应该把颜色 xy 中下标数量少的合并到另外一个中,如果颜色 x 的 std::vector 小于颜色 ystd::vector 直接合并即可,同时把颜色是 xai 修改成 y。否则我们应该把颜色 y 的下标合并到颜色 x 中,这样做的话要把颜色是 yai 修改成 x,这样做是没问题的,并不会影响颜色段的数量,但问题是如果下次询问的颜色是 y 就会出错,因为此时 a 中没有颜色 y,都用 x 来表示了。为此我们可以维护一个数组 ccx 表示颜色 x 实际上用 cx 来表示。所以如果遇到颜色 xstd::vector 大于颜色 ystd::vector 的情况,我们应该让颜色 x 用下标少的颜色 cy 来表示,为此只需交换 cxcy,然后把颜色是 cx 的下标合并到颜色 cy 中即可,同时把颜色是 cxai 修改成 cy

  AC 代码如下,时间复杂度为 O(nlogn)

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 1e5 + 10, M = 1e6 + 10;

int a[N];
vector<int> p[M];
int c[M];

int main() {
    int n, m;
    scanf("%d %d", &n, &m);
    int ret = 0;
    for (int i = 1; i <= n; i++) {
        scanf("%d", a + i);
        c[a[i]] = a[i];
        p[a[i]].push_back(i);
        if (a[i] != a[i - 1]) ret++;
    }
    while (m--) {
        int op;
        scanf("%d", &op);
        if (op == 1) {
            int x, y;
            scanf("%d %d", &x, &y);
            if (x == y) continue;
            if (p[c[x]].size() > p[c[y]].size()) swap(c[x], c[y]);
            for (auto &i : p[c[x]]) {
                if (a[i - 1] == c[y]) ret--;
                if (a[i + 1] == c[y]) ret--;
            }
            for (auto &i : p[c[x]]) {
                a[i] = c[y];
                p[c[y]].push_back(i);
            }
            p[c[x]].clear();
        }
        else {
            printf("%d\n", ret);
        }
    }
    
    return 0;
}

 

参考资料

  数据结构学习笔记(8) 启发式合并:https://zhuanlan.zhihu.com/p/560661911

  题解 P3201 【[HNOI2009]梦幻布丁】:https://siyuan.blog.luogu.org/solution-p3201

posted @   onlyblues  阅读(48)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
Web Analytics
点击右上角即可分享
微信分享提示