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;
}