题目描述:
N个布丁摆成一行,进行M次操作.每次将某个颜色的布丁全部变成另一种颜色的,然后再询问当前一共有多少段颜色.例如颜色分别为1,2,2,1的四个布丁一共有3段颜色.
输入格式:
第一行给出N,M表示布丁的个数和好友的操作次数. 第二行N个数A1,A2…An表示第i个布丁的颜色从第三行起有M行,对于每个操作,若第一个数字是1表示要对颜色进行改变,其后的两个整数X,Y表示将所有颜色为X的变为Y,X可能等于Y. 若第一个数字为2表示要进行询问当前有多少段颜色,这时你应该输出一个整数.
输出格式:
针对第二类操作即询问,依次输出当前有多少段颜色.
HINT:
1 <= n,m <= 100,000
0 < Ai,x,y < 1,000,000
思路:
对于每一种颜色,建立一个链表,存出现的位置
所以每一次修改操作为一次两个链表的合并
如果我们使用启发式合并,即每一次把链表短的合并到链表长的里面
其时间复杂度为
O(nlog(n))
,即每一个布丁最多被合并
log(n)
次,因为每一次会把短的链至少扩大一倍(均摊法)
至于颜色的问题,记一个id[]数组,每一次保证可以让短链合并到长链上(如果反了,交换id数组即可)
感想:
是不是只有我在开始的时候狂想线段树,LCT,splay什么的
然后写了一个莫名其妙的线段树,莫名其妙地拿了和大暴力一样的分
于是万念俱灰地去看题解,看到均摊和启发式合并我开始怀疑自己的智商了
学习均摊了。。效率真有趣。。
代码:
//miaomiao 3.2
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
#define Set(a, v) memset(a, v, sizeof(a))
#define For(i, a, b) for(int i = (a); i <= (int)(b); i++)
#define N (1000000+5)
int a[N], head[N], nxt[N], tail[N], cnt[N], id[N];
void swp(int &a, int &b){a^=b^=a^=b;}
int main(){
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
#endif
int n, m, x, y, op, ans = 0;
scanf("%d%d", &n, &m);
For(i, 1, n){
scanf("%d", &a[i]);
if(a[i]!=a[i-1]) ans++; cnt[a[i]]++;
if(!head[a[i]]) tail[a[i]] = i;
nxt[i] = head[a[i]]; head[a[i]] = i;
}
For(i, 1, N-5) id[i] = i;
while(m--){
scanf("%d", &op);
if(op == 2){
printf("%d\n", ans); continue;
}
scanf("%d%d", &x, &y);
if(x == y) continue;
if(cnt[id[x]] > cnt[id[y]]) swp(id[x], id[y]);
x = id[x], y = id[y];
if(!cnt[x]) continue;
cnt[y] += cnt[x];
for(int i = head[x]; i; i=nxt[i]){
if(a[i-1]==y) ans--;
if(a[i+1]==y) ans--;
}
for(int i = head[x]; i; i=nxt[i]) a[i] = y;
nxt[tail[y]] = head[x]; tail[y] = tail[x];
tail[x] = head[x] = cnt[x] = 0;
}
return 0;
}