夢幻布丁

最爱丁珰發表於2024-08-11

假設現在有\(n\)個元素,每個元素最開始單獨成為一個集合

現在有一種合併操作,可以合併兩個集合,假設將集合\(A\)合併到集合\(B\),那麼時間複雜度為\(O(|A|p)\),其中\(O(p)\)表示合併一個元素的操作的複雜度,也就是說我們的操作每次是合併一個元素和一個集合,所以我們將\(A\)合併到\(B\)裡,就要對\(A\)中每個元素作合併操作,於是時間複雜度為\(O(|A|p)\)

假設我們依次合併(i.e.先將\(\left\{1\right\}\)合併到\(\left\{2\right\}\),再將\(\left\{1\space2\right\}\)合併到\(\left\{3\right\}\),再將\(\left\{1\space2\space 3\right\}\)合併到\(\left\{4\right\}\),以此類推),那麼不難知道時間複雜度為\(O(n^2p)\)

但是我們現在採取一個最佳化,每次合併都將元素個數少的集合合併到元素個數多的集合裡面,這樣的話時間複雜度就會變為\(O(n\log np)\)

證明:考慮每個元素對時間複雜度的貢獻,為\(O(ap)\),其中\(a\)是這個元素被合併的次數,而這顯然就等於其所在的不同集合的個數;考慮其所在的不同集合一共有多少個,由於每次合併是將小的集合合併到大的集合裡面,那麼每次小的集合的元素個數至少乘以\(2\),所以\(a\)的上界為\(O(\log n)\),於是得證

然後來考慮這道題目。注意,我們的啟發式合併每次是合併一個元素的,所以我們應該考慮假設我們每次只改變一個元素的顏色(而不是所有這個元素的顏色一起改變)答案會發生什麼變化

比較顯然,設\(cnt\)為改變之前的答案,當前將元素\(i\)\(i\)是下標)的顏色\(x\)改變為\(y\),那麼如果\(i-1\)的顏色為\(y\)\(cnt\)會減一;如果\(i+1\)的顏色為\(y\)\(cnt\)會減一

於是我們現在就可以找到一個\(O(n^2)\)的暴力演算法了,考慮用啟發式合併最佳化

由於啟發式合併會將更小的集合合併到更大的集合,涉及到集合的快速交換,所以我們需要一個雜湊表h[i]來表示某一種顏色的位置,並且再用一個單獨的陣列p[i]來表示這個顏色對應的雜湊表指標(這樣在交換集合的時候就可以\(O(1)\)交換p了)。注意h[i]並不是表示顏色\(i\)的位置,只有p[i]才表示顏色\(i\)p[i]指向的雜湊表表示\(i\)的位置(p[i]不一定指向h[i]),然後剩下的可以看y總的程式碼;注意其程式碼的color陣列並不是表示真實的顏色,color[i]!=color[j]只能說明位置\(i,j\)的顏色不同,但是並不表示位置\(i,j\)的顏色分別為color[i]color[j],所以交換的操作顯然正確,而且交換之後ans顯然不變;還要注意merge函式的參數列裡面是引用

但是有一種用vector的更簡單的寫法。設vector<int> g[N],其中g[i]表示顏色\(i\)的位置,color的意義與y總程式碼相同;然後利用vecotr的成員函式swap進行高效交換(這個時間複雜度是\(O(1)\)的,原因好像是因為交換的指標)即可,程式碼見下

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e6+10;
const ll mod=1e9+7;
int n,m;
int cnt,color[N];
vector<int> g[N];
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&color[i]);
		g[color[i]].push_back(i);
		cnt+=(color[i]!=color[i-1]);
	}
	while(m--)
	{
		int op;
		scanf("%d",&op);
		if(op==1)
		{
			int x,y;
			scanf("%d%d",&x,&y);
			if(x==y) continue;
			if(g[x].size()>g[y].size()) g[x].swap(g[y]);
			//假設顏色x的數量更多,就要交換
			//但是題目要求的是將x變成y不是將y變成x
			//所以此時認為,原來是x的位置現在全部變成y,原來是y的位置現在全部變成x
			//易知此時cnt不變 
			//然後再將此時顏色x全部變成顏色y即可 
			int v=color[g[y].back()];
			for(int i=0;i<g[x].size();i++) 
			cnt-=(color[g[x][i]-1]==v)+(color[g[x][i]+1]==v);
			while(g[x].size())
			{
				color[g[x].back()] = v;
                g[y].push_back(g[x].back());
                g[x].pop_back();
			}
		}
		else printf("%d\n",cnt);
	}
	return 0;
} 

相關文章