启发式合并

启发式合并

并查集

按轶合并

数组合并

P3201 [HNOI2009] 梦幻布丁

考虑每次把数量小的颜色合并到数量大的颜色上去

每次合并不同颜色 \(sz\) 至少 \(\times 2\),最多合并 \(O(\log n)\) 次,复杂度为 \(O(n\log n)\)

我们考虑如何把数量大合并到数量小的变成数量小的合并成数量大的上面

我们用 \(vec[col]\) 存所有 \(a_i=col\)\(i\),考虑到 \(vec\) 实际上是个指针的特性,我们可以直接交换 \((x,y)\)\(vec\) 指针,这是 \(O(1)\)

if(col[x].size()>col[y].size())
	col[x].swap(col[y]);

考虑计算答案的贡献

\(\text{1 2 1}\)

\(2\) 改成 \(1\) 的贡献是 \(-2\)

\(\text{1 2 3}\)

\(2\) 改成 \(1\) 的贡献是 \(-1\)

归纳可得,贡献为 \(x\) 颜色两侧 \(y\) 颜色的个数

#include<bits/stdc++.h>
#define pt putchar(' ')
#define nl puts("")
#define pi pair<int,int>
#define pb push_back
#define go(it) for(auto &it:as[x])
using namespace std;

const int N=1e5+10;
int n,m,x,y,ans;
int a[N];
vector<int> col[N];

int fr(){
    int x=0,flag=1;
    char ch=getchar();
    while(ch<'0' || ch>'9'){
        if(ch=='-') flag=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9'){
        x=x*10+(ch-'0');
        ch=getchar();
    }
    return x*flag;
}
void fw(int x){
	if(x<0) putchar('-'),x=-x;
    if(x>9) fw(x/10);
    putchar(x%10+'0');
}
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}

void merge(int x,int y)
{
	if(x==y) return; //保证复杂度
	if(col[x].size()>col[y].size())
		col[x].swap(col[y]);
	if(!col[x].size()) return; //防空指针
	
	int c=a[col[y].back()];
	for(auto &i:col[x]) //计算贡献
		ans-=(a[i-1]==c)+(a[i+1]==c);
	
	while(col[x].size())
	{
		a[col[x].back()]=c;
		col[y].pb(col[x].back());
		col[x].pop_back();
	}
}

int main()
{
	n=fr(),m=fr();
	for(int i=1;i<=n;i++)
	{
		a[i]=fr();
		col[a[i]].pb(i);
		ans+=(a[i]!=a[i-1]);
	}
	
	for(int i=1;i<=m;i++)
	{
		if(fr()&1)
		{
			x=fr(),y=fr();
			merge(x,y);
		}
		else fw(ans),nl;
	}

	return 0;
}
posted @ 2023-09-22 21:04  xyzfrozen  阅读(55)  评论(0)    收藏  举报