1. 程式人生 > >【BZOJ1483】夢幻布丁(HNOI2009)-連結串列+啟發式合併

【BZOJ1483】夢幻布丁(HNOI2009)-連結串列+啟發式合併

測試地址:夢幻布丁
做法: 本題需要用到連結串列+啟發式合併。
首先,注意到顏色的段數等於,相鄰的顏色不同的元素對數 + 1 +1 ,而一對元素一旦顏色相同就不可能再變成不同,因此我們在改變顏色的時候,只要找到這個顏色的元素周圍有沒有修改後就變成顏色相同的元素即可。
我們想到用連結串列來連線同顏色的那些點,但如果進行暴力修改的話,時間複雜度最壞為 O

( n 2 ) O(n^2) ,怎麼辦呢?注意到一種顏色變成另一種顏色,實際上就是這兩種顏色的連結串列進行合併,我們可以把點數較小的那個顏色的點改變成另一種顏色,這樣就是啟發式合併的複雜度: O
( n log n ) O(n\log n)
。然而最後變成的顏色可能和原來它想表示的顏色不同,因此需要額外開一個數組 t
t
來儲存在資料中出現的顏色,在我們的演算法中實際上表示的顏色是什麼,如果出現了被改變的顏色不是原來它想改變的顏色的情況,交換 t ( x ) t(x) t ( y ) t(y) 即可。
我傻逼的地方:連結串列都能寫掛…GG退役滾粗一氣呵成…
以下是本人程式碼:

#include <bits/stdc++.h>
using namespace std;
int n,m,head[1000010],tail[1000010]={0},ans=1;
int nxt[1000010]={0},siz[1000010]={0},c[1000010],t[1000010];

void solve(int x,int y)
{
	for(int i=head[x];i;i=nxt[i])
	{
		if (c[i-1]==y) ans--;
		if (c[i+1]==y) ans--;
	}
	for(int i=head[x];i;i=nxt[i])
		c[i]=y;
	nxt[tail[y]]=head[x];
	tail[y]=tail[x];
	siz[y]+=siz[x];
	siz[x]=head[x]=tail[x]=0;
}

int main()
{
	int tot=0;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&c[i]);
		siz[c[i]]++;
		if (tail[c[i]]) nxt[tail[c[i]]]=i;
		else head[c[i]]=i,t[c[i]]=c[i];
		tail[c[i]]=i;
		if (i>1&&c[i]!=c[i-1]) ans++;
	}
	
	for(int i=1;i<=m;i++)
	{
		int op,x,y;
		scanf("%d",&op);
		if (op==1)
		{
			scanf("%d%d",&x,&y);
			if (x==y) continue;
			if (siz[t[x]]>siz[t[y]])
				swap(t[x],t[y]);
			if (siz[t[x]]==0) continue;
			solve(t[x],t[y]);
		}
		else printf("%d\n",ans);
	}
	
	return 0;
}