标签:
启发式合并就是一个简单的思想, 就是每次在合并的时候 都把 小的合并到大的里面。 可以证明这个方法在很多时候都可以把合并次数 为 n 的问题优化到 logn次。
应用
一) 并查集
并查集的按秩合并就是记录一下每个点的size 是多大然后每次 把 size 较小的那个接到较大的上面。 但是好像并不是很常用?
二) 链表
bzoj 1483: [HNOI2009]梦幻布丁
一个序列上有一些颜色, 每次把 颜色 为a 的都涂成 颜色 b, 询问当前的序列中有多少块颜色。
对于每一种颜色建一个链表存这个颜色覆盖的所有位置, 每次修改把 a 链表上的所有点都改成 b, 并且把 a 接到b 上面。复杂度是 n2 的。
用启发式合并优化就是每次都改较小的那个, 脑补一下的确可以做到 nlogn。
实现的时候因为一次覆盖操作后 a 和 b 原本的两种颜色都变成了一种, 所以记录一下每种颜色当前代表的是什么颜色就可以了。显然在合并前的序列中每出现一组相邻的 a, b, 就会对答案产生 -1 的贡献, 而无论是把 a 都变成 b , 还是把 b 都变成a, 不会影响对这个贡献的统计。 修改颜色的时候顺便统计一下就好了。
#include <iostream>
#include <cstdio>
#include <algorithm>
#define MAXM 1000005
using namespace std;
int n, m, c[MAXM], head[MAXM], fact[MAXM], st[MAXM], next[MAXM], s[MAXM], ans;
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++){
scanf("%d", &c[i]);
if(c[i] != c[i - 1])ans ++;
s[c[i]] ++;
if(!head[c[i]]) st[c[i]] = i;
next[i] = head[c[i]]; head[c[i]] = i;
fact[c[i]] = c[i];
}
while(m --){
int k; scanf("%d", &k);
if(k == 2)printf("%d\n", ans);
else{
int a, b; scanf("%d%d", &a, &b);
if(a == b)continue;
if(s[fact[b]] < s[fact[a]])swap(fact[b], fact[a]);
a = fact[a], b = fact[b];
if(!s[a])continue;
s[b] += s[a]; s[a] = 0;
for(int i = head[a]; i; i = next[i]){
if(c[i - 1] == b)ans --;
if(c[i + 1] == b)ans --;
}
for(int i = head[a]; i; i = next[i]) c[i] = b;
next[st[b]] = head[a]; st[b] = st[a];
head[a] = st[a] = 0;
}
}
return 0;
}
标签:
原文地址:http://www.cnblogs.com/lixintong911/p/4250222.html