2154. 梦幻布丁

题目链接

2154. 梦幻布丁

题目描述

\(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\) 不是颜色的编号上限。

解题思路

启发式合并

首先证明启发式复杂度:给定 \(m\) 个集合,共 \(n\) 个元素。要将集合合并为一个集合,考虑每个数的贡献,假设合并一个数的复杂度为 \(O(op)\),启发式合并每次将大小较小的那个集合合并到大小更大的那个集合,设大小更小的那个集合大小为 \(x\),则合并后的集合大小至少为 \(2\times x\)
,则每个数的操作次数至多为 \(logn\) 次即可把整个元素合并在一起,故整体时间复杂度为 \(O(nlogn)\)

本题要求解颜色块的数量,可将所有颜色所在的编号用单链表表示,当要求 \(x\) 变为 \(y\) 时,采取启发式合并,如果 \(x\) 集合大小小于 \(y\) 集合大小,则可以直接遍历 \(x\) 颜色集合所有位置,更新答案,并将 \(x\) 表示的链表接在 \(y\) 上面,\(\color{red}{但是如果\ x\ 的集合大小不小于\ y\ 集合大小怎么办?}\)可以设定一个方向数组,即将所有对颜色的操作时即先用方向数组转换为对应的颜色编号,初始时,每种颜色指向初始颜色编号,一旦出现 \(x\) 集合大小不小于 \(y\) 集合大小的情况,而对于将 \(x\) 变为 \(y\) 与将 \(y\) 变为 \(x\),对答案的贡献一样,即求解的方式一样,故直接交换方向数组即可

  • 时间复杂度:\(O(nlogn)\)

代码

// Problem: 梦幻布丁
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/2156/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
 
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
 
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

const int N=1e5+5,M=1e6+5;
int n,m,color[N],h[M],ne[N],e[N],sz[M],cnt,res,mp[M];
void add(int a,int b)
{
	e[cnt]=b,ne[cnt]=h[a],h[a]=cnt++;
}
void merge(int &x,int &y)
{
	if(sz[x]>sz[y])swap(x,y);
	for(int i=h[x];~i;i=ne[i])
	{
		int j=e[i];
		res-=(color[j-1]==y)+(color[j+1]==y);
	}	
	for(int i=h[x];~i;i=ne[i])
	{
		int j=e[i];
		color[j]=y;
		if(ne[i]==-1)
		{
			ne[i]=h[y],h[y]=h[x];
			break;
		}
	}
	sz[y]+=sz[x],sz[x]=0,h[x]=-1;
}
int main()
{
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof h);
    for(int i=1;i<=n;i++)
    {
    	scanf("%d",&color[i]);
    	add(color[i],i);
    	sz[color[i]]++;
    	res+=color[i]!=color[i-1];
    }
    for(int i=0;i<M;i++)mp[i]=i;
    while(m--)
    {
    	int op,x,y;
    	scanf("%d",&op);
    	if(op==2)printf("%d\n",res);
    	else
    	{
    		scanf("%d%d",&x,&y);
    		if(x!=y)merge(mp[x],mp[y]);	
    	}
    }
    
    return 0;
}
posted @ 2022-07-16 16:12  zyy2001  阅读(52)  评论(0编辑  收藏  举报