启发式合并:它的本质很简单:每次把短的合并到长的上面,那么合并一次的复杂度为 O(|短的队列|)O(∣短的队列∣)
就是一种思想 有n个集合, 我们每次进行合并操作 ,总是希望把小的合并到大的 ,这样得到的集合一定是 原来集合的2倍 所以 每个元素被复杂度是logn的 ,有n个元素 所以总的时间复杂度是onlogn 。
上面是一个板子题
题目描述
n 个布丁摆成一行,进行 m 次操作。每次将某个颜色的布丁全部变成另一种颜色的,然后再询问当前一共有多少段颜色。
例如,颜色分别为 1,2,2,1的四个布丁一共有 3 段颜色.
输入格式
第一行是两个整数,分别表示布丁个数 n 和操作次数 m。
第二行有 n 个整数,第 ii 个整数表示第 i个布丁的颜色 a
接下来 m 行,每行描述一次操作。每行首先有一个整数 op 表示操作类型:
- 若 op = 1,则后有两个整数 x, y,表示将颜色 x 的布丁全部变成颜色 y
- 若 op = 2,则表示一次询问。
输出格式
对于每次询问,输出一行一个整数表示答案。
我们的做法是启发式合并 ,个人理解,就是 他并不在乎颜色,他只在乎段数 ,我们可以开一个pos数组 记录每个颜色他所在的下标,然后进行合并操作的时候,判断两个颜色size的 大小 如果大的容进小的,我们可以互换 pos ,然后进行操作,,如果是空的 我们直接continue, 写个modify函数。 把ans 记录一下,
ans-= (a[p]!=a[p-1]) + (a[p]!=a[p+1]);
a[p] = col;
ans += (a[p] != a[p-1])+(a[p]!=a[p+1]);
对应的a 就是我们原来记录的数组, 这里的p 就是从pos[x]拿出来得到的位置,并对对应的位置修改。 很后面代码详细看下。 也就是说 我们修改a数组是 ,从小到大合并,但实际并不是这个 ,我们实际的对应在pos中体现 在修改后,我们把pos[x] 给清除。, 下面贴出ac代码
# include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 101000;
const int M = 1010000;
vector<int>pos[M];
int n,m,a[N],ans;
int main(){
scanf("%d%d",&n,&m);
for(int i = 1 ;i<=n;i++){
scanf("%d",&a[i]);
pos[a[i]].push_back(i);
}
for(int i = 1 ;i<=n+1;i++){
ans += a[i] != a[i-1];
}
for (int i =0;i<m;i++){
int op;
scanf("%d",&op);
if ( op == 2){
printf("%d\n",ans-1);
}else{
int x ,y;
scanf("%d%d",&x,&y);
if(x == y) continue;
if(pos[x].size() > pos[y].size()){
pos[x].swap(pos[y]);
}
if(pos[y].empty()) continue;
auto modify = [&](int p ,int col){
ans-= (a[p]!=a[p-1]) + (a[p]!=a[p+1]);
a[p] = col;
ans += (a[p] != a[p-1])+(a[p]!=a[p+1]);
};
int col = a[pos[y][0]];
for (int p : pos[x]){
modify(p,col);
pos[y].push_back(p);
}
pos[x].clear();
// 很巧妙 a 全部变成了 2 2 2 2 2 虽然要把2 变成1 但其实变得是1 到2 但是pos 数组记录的是实际操作的。。
}
}
return 0;
}
后记 :感觉自己表达不太好,写题解真的需要下很多功夫。。orz (给大佬跪了 %%%