(这篇题解可能没什么营养,主要是记录一下我用map乱搞启发式合并的神奇做法)

首先我们知道,我们肯定要用一堆集合维护每一种数当前的位置,并支持合并和数连续出现的段数两种操作

我发现这个东西并不好搞,但是暴力维护是 \(O(n)\)

所以我们就要用到启发式合并

启发式合并本身是一个非常naive的trick

我们考虑我们把 \(a\) 数出现的位置集合合并到b数出现的位置集合,相当于插入每一个 \(a\) 所在的位置并更新段数

所以复杂度只与 \(a\) 数出现的位置集合的大小相关

然后我们发现把 \(a\) 合并到 \(b\) 和把 \(b\) 合并到 \(a\) 的结果是一样的

所以我们可以贪心的把小的合并到大的上

我们考虑这样操作时的均摊复杂度

我们知道所有数的位置总数是 \(n\)

然后每次把 A 集合暴力合并到 B 集合后, A 中的元素所在的集合大小起步翻倍

所以每个元素最多被转移log次

所以总的复杂度是\(O(nlogn)\)

这是启发式合并的相关内容

然后接下来就是我自己的乱搞部分啦

我们发现最简单的维护每个数出现的位置的方法是开 \(n\) 个数组

然后每个数组在改颜色出现的位置上标 1 ,其他地方标 0

但是这样开不下

当然有很多方法解决这个问题比如 vector 或者链表什么的

但是要保持这个维护写法怎么办呢

我们发现所有数组里只有 \(n\) 个位置有值,于是掏出一个神器 map !

每次转移完记得清空即可(map永远的神!)

当然set也可以qaq

//Talking to the moon
#include <bits/stdc++.h>
#define N 1000010
#define M 2000010
#define int long long
#define int_edge int to[M],val[M],nxt[M],head[N],cnt=0;
using namespace std;
int n,m,a[N],f[N],ans=0;
map<int,int>mp[N];
int read(){
	int fu=1,ret=0;char ch;
	for(;!isdigit(ch);ch=getchar())if(ch=='-')fu*=-1;
	for(;isdigit(ch);ch=getchar())ret=(ret<<1)+(ret<<3)+ch-'0';
	return ret*fu;
}
signed main()
{
	n=read();m=read();
	for(int i=1;i<N;i++)f[i]=i;
	for(int i=1;i<=n;i++)
	{
		a[i]=read(),mp[a[i]][i]=1;
		if(a[i-1]!=a[i])ans++;
	}
	while(m--){
		int opt=read();
		if(opt==2)printf("%lld\n",ans);
		else{
			int x=read(),y=read();
			if(mp[f[x]].size()>mp[f[y]].size())swap(f[x],f[y]);	
			if(x==y||mp[f[x]].empty())continue;
			for(auto i=mp[f[x]].begin();i!=mp[f[x]].end();i++)ans-=(mp[f[y]].find((*i).first+1)!=mp[f[y]].end())+(mp[f[y]].find((*i).first-1)!=mp[f[y]].end());
			auto i=mp[f[x]].begin();while(i!=mp[f[x]].end())mp[f[y]][(*i).first]++,i=mp[f[x]].erase(i);
		}
	}
	return 0;
}

相关文章:

  • 2021-10-16
  • 2021-11-25
  • 2021-06-27
  • 2021-09-16
  • 2022-02-21
  • 2021-06-02
  • 2021-10-18
猜你喜欢
  • 2021-09-10
  • 2021-10-15
  • 2022-02-12
  • 2021-06-17
  • 2022-02-15
  • 2021-12-28
  • 2022-12-23
相关资源
相似解决方案