标签:
启发式合并就是一个简单的思想, 就是每次在合并的时候 都把 小的合并到大的里面。 可以证明这个方法在很多时候都可以把合并次数 为 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