Morituri te salutant

洛谷 P3201 梦幻布丁 题解

(这篇题解可能没什么营养,主要是记录一下我用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;
}
posted @ 2021-11-11 22:02  shight  阅读(33)  评论(0编辑  收藏  举报