1. 程式人生 > 實用技巧 >#啟發式合併,連結串列#洛谷 3201 [HNOI2009] 夢幻布丁

#啟發式合併,連結串列#洛谷 3201 [HNOI2009] 夢幻布丁

題目

\(n\)個布丁擺成一行,進行\(m\)次操作。
每次將某個顏色的布丁全部變成另一種顏色的,
然後再詢問當前一共有多少段顏色。
\(n,m\leq 10^5,col\leq 10^6\)


分析

考慮用連結串列儲存每一種顏色的位置,由於顏色總數只會減少不會增多,
考慮啟發式合併,將個數小的合併到個數大的,並交換實際的顏色表示,
時間複雜度\(O(nlog_2n)\)


程式碼

#include <cstdio>
#include <cctype>
#define rr register
using namespace std;
const int N=1000011;
int ls[N],st[N],cnt[N],col[N/10],f[N],nxt[N/10],n,m,ans;
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
inline void print(int ans){
	if (ans>9) print(ans/10);
	putchar(ans%10+48);
}
inline void Turn_Into(int x,int y){
	for (rr int i=ls[x];i;i=nxt[i]) ans-=(col[i-1]==y)+(col[i+1]==y);//如果相鄰塊數減小
	for (rr int i=ls[x];i;i=nxt[i]) col[i]=y;//更改顏色
	nxt[st[x]]=ls[y],ls[y]=ls[x],cnt[y]+=cnt[x],ls[x]=st[x]=cnt[x]=0;//更新連結串列
}
signed main(){
	n=iut(); m=iut();
	for (rr int i=1;i<=n;++i){
		col[i]=iut(),ans+=col[i]!=col[i-1];
		if (!ls[col[i]]) st[col[i]]=i,f[col[i]]=col[i];
		++cnt[col[i]],nxt[i]=ls[col[i]],ls[col[i]]=i;
	}
	while (m--){
		rr int opt=iut();
		if (opt==2) print(ans),putchar(10);
		else{
			rr int x=iut(),y=iut();
			if (x==y) continue;//顏色相同不需要合併
			if (cnt[f[x]]>cnt[f[y]])
				f[x]^=f[y],f[y]^=f[x],f[x]^=f[y];//個數小的合併到個數大的
			if (!cnt[f[x]]) continue;//不需要合併
			Turn_Into(f[x],f[y]);
		}
	}
	return 0;
}