HNOI2009 梦幻布丁

题目链接

题解

膜一发启发式合并
启发式合并从前我都只在并查集上用到过,但今天是在这种上面…
这道题的做法非常暴力:直接对每一个颜色节点构建链式前向星,然后合并的时候短链合并到长链上。
为什么这么做是对的呢?因为短链合并之后最少比原来要长一倍,所以合并起来最多合并$logn$次,这样均摊总时间复杂度就是$O(nlogn)$。
感觉这种思想跟线段数的区间开方的分析有着异曲同工之妙…
有一个细节:要记录合并时操作的真正颜色,因为短合长或者长合短在程序操作上是等价的,但颜色是不等价的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <cctype>
#define INF 2000000000
using namespace std;
typedef long long ll;
int n,m,id[1000005],ans,a[100005]={0};
int cnt[1000005]={0},at[1000005]={0},_nex[100005];
int read(){
int f=1,x=0;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-f;c=getchar();}
while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
return f*x;
}
void _merge(int x,int y){
if(x==y)return ;
if(cnt[id[x]]>cnt[id[y]])swap(id[x],id[y]);
x=id[x],y=id[y];
if(!cnt[x])return ;
//x,y号元素代表的真正颜色(程序操作上)
cnt[y]+=cnt[x],cnt[x]=0;
//x合并到y上
for(int i=at[x];i;i=_nex[i]){
if(a[i-1]==y)ans--;
if(a[i+1]==y)ans--;

}
for(int i=at[x];i;i=_nex[i]){
a[i]=y;
if(!_nex[i]){
_nex[i]=at[y];
break;
}
}
at[y]=at[x],at[x]=0;
}
void init(){
n=read(),m=read();
int col;
for(int i=1;i<=n;i++){
a[i]=read(),col=a[i];
if(a[i]!=a[i-1])ans++;
cnt[col]++,_nex[i]=at[col],at[col]=i;
}
for(int i=1;i<=1000000;i++)id[i]=i;
}
void solve(){
int opr,x,y;
for(int i=1;i<=m;i++){
opr=read();
if(opr==2)printf("%d\n",ans);
else x=read(),y=read(),_merge(x,y);
}
}
int main(){
init();
solve();
return 0;
}