首页 > 其他分享 >[HNOI2009] 梦幻布丁

[HNOI2009] 梦幻布丁

时间:2023-12-09 18:11:24浏览次数:40  
标签:std 下标 颜色 布丁 梦幻 合并 vector HNOI2009

[HNOI2009] 梦幻布丁

题目描述

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

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

输入格式

第一行是两个整数,分别表示布丁个数 $n$ 和操作次数 $m$。  
第二行有 $n$ 个整数,第 $i$ 个整数表示第 $i$ 个布丁的颜色 $a_i$。  
接下来 $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]$ 一段颜色。

数据规模与约定

对于全部的测试点,保证 $1 \leq n, m \leq 10^5$,$1 \leq a_i ,x, y \leq 10^6$。

提示

请注意,不保证颜色的编号不大于 $n$,也不保证 $x \neq y$,$m$ 不是颜色的编号上限。

 

解题思路

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

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

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

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

  AC 代码 时间复杂度为 $O(n \log{n})$:

#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

标签:std,下标,颜色,布丁,梦幻,合并,vector,HNOI2009
From: https://www.cnblogs.com/onlyblues/p/17891269.html

相关文章

  • P3202 [HNOI2009] 通往城堡之路
    考虑将每个支撑点都先设成其下限高度,即\(h_i\getsh_1-(i-1)\timesd\),这样就只会提高某些支撑点的高度。显然每次提高的是一个后缀。提高某个后缀的贡献是当前高度低于原先高度的支撑点数量减去当前高度不低于原先高度的支撑点数量。选择贡献最大的后缀直到最后一个支撑点的高......
  • 梦幻西游手游详细图文架设教程
    前言提到梦幻西游,大家肯定不陌生。在2001年正式上线,它成为了很多人的第一款网游,陪伴了一代又一代的玩家成长。没错,今天要架设的就是梦幻西游手游!本文讲解梦幻西游手游架设教程,经典的职业、音乐、场景、玩法,就在梦幻西游!我架设的梦幻西游手游请关注我的公众号echeverra,发......
  • P3200 [HNOI2009] 有趣的数列
    原题这题我\(O(n^2)\)的做法竟然没有想出来,反思QwQ首先\(O(n^2)\)的做法很好想,我们考虑从小到大往数组里填数,显然我们要求任何时刻编号为奇数的位置要填的比编号为偶数的位置要不少才行于是我们设\(dp_{i,j,k}\)表示填了前\(i\)个数,奇数位填的个数为\(j\),偶数位填的个数为\(k\)......
  • P3201 [HNOI2009] 梦幻布丁 启发式合并,时间复杂度
    [HNOI2009]梦幻布丁一种很暴力,很容易想到,但时间复杂度不对的做法:既然每一次修改是以颜色作为单位的,那就用set或者链表(vector)维护每一个颜色出现的位置。将颜色\(x\)改为\(y\)的时候,遍历\(list_x\)的每一个点,判断其左右是否为\(y\),更新ans(不同颜色块数量)时间复杂度最大为......
  • P4729 [HNOI2009] 积木游戏
    P4729[HNOI2009]积木游戏Solution2023.09.06。八个月前做这个题调了六个小时。现在看来,除开欧拉定理的部分,整道题的思路极其清晰易懂,虽然码量大,但并不难码。尽管如此,融合了数据结构、图论(模型构建+三元环计数)、拓扑论(欧拉定理)多方面知识点,而且还有四面共角的细节问题,它仍然......
  • TC脚本开发-梦幻诛仙自动登录思路梳理及源代码
    界面如下:设计思路是:最多5个号自动登录,从帐号一开始登记帐号,放入数组中。登记完之后点击登录 判断帐号数组中有多少个帐号,先后进行登录,调用自动登录函数。自动登录函数启动进程,根据图片点击按钮,根据角色变量来选择角色,点击进入游戏。代码如下:空间自动登录ts=com("ts.tssoft")......
  • 梦幻布丁
    2154.梦幻布丁考虑先维护连续段数。我们可以先搞几个单链表,每个单链表存储的是每种颜色处于的所有位置,由于连续段数等于相邻两数不同的个数,我们可以先算出初始的情况,然后对于每次修改,暴力的将一种颜色的所有位置修改成另外一种颜色,这种操作一定不会使得答案增加,所以我们考虑怎......
  • 牧云 • 主机管理助手|正式开放应用市场,梦幻联动雷池WAF等多款开源软件
     0x00 前言上个月,我司长亭开源了雷池WAF,不到三天就吸引了超过上千个师傅使用,几个交流群里,师傅们讨论的热火朝天,其中两个话题引起了我们牧云 • 主机管理助手 ( Collie ) 团队的关注:没有新主机安装雷池安装配置麻烦,希望有一键安装的脚本 别着急, Collie 会出手:......
  • Neopixel组件的应用 -- 梦幻的"七彩灯带"
    项目背景micro:bit的扩展组件中有一个"Neopixel"彩带控件,利用DFROBOT套件中的"七彩灯带",设计一个梦幻的灯带来点亮生活,装饰环境吧编程实践1.材料准备:1张micro:bit开发板,1张DFROBOT扩展板,1根导线,1根七彩灯带2.添加"扩展"组件"Neopixel"(1)点击"扩展"选项(2)选择"Neopixel"组......
  • 【题解】[HNOI2007]梦幻岛宝珠
    题目分析:对于这种某一个值很大另一个值很小的背包题,就是要求找特殊性质。既然每一个\(w\)都可以写成\(a\times2^b\)的性质,就可以对于每一个\(b\)单独做背包,这样......