测试地址:梦幻布丁
做法: 本题需要用到链表+启发式合并。
首先,注意到颜色的段数等于,相邻的颜色不同的元素对数
+
1
+1
+1,而一对元素一旦颜色相同就不可能再变成不同,因此我们在改变颜色的时候,只要找到这个颜色的元素周围有没有修改后就变成颜色相同的元素即可。
我们想到用链表来连接同颜色的那些点,但如果进行暴力修改的话,时间复杂度最坏为
O
(
n
2
)
O(n^2)
O(n2),怎么办呢?注意到一种颜色变成另一种颜色,实际上就是这两种颜色的链表进行合并,我们可以把点数较小的那个颜色的点改变成另一种颜色,这样就是启发式合并的复杂度:
O
(
n
log
n
)
O(n\log n)
O(nlogn)。然而最后变成的颜色可能和原来它想表示的颜色不同,因此需要额外开一个数组
t
t
t来存储在数据中出现的颜色,在我们的算法中实际上表示的颜色是什么,如果出现了被改变的颜色不是原来它想改变的颜色的情况,交换
t
(
x
)
t(x)
t(x)和
t
(
y
)
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;
}