并查集 poj1988 Cube Stacking(并查集
使用并查集查找时,如果查找次数很多,那么使用朴素版的查找方式肯定要超时。比如,有一百万个元素,每次都从第一百万个开始找,这样一次运算就是10^6,如果程序要求查找个一千万次,这样下来就是10^13,肯定要出问题的。
这是朴素查找的代码,适合数据量不大的
int findx(int x) { int r=x; while(parent[r] !=r) r=parent[r]; return r; }
下面是采用路径压缩的方法查找元素:
int find(int x) //查找x元素所在的集合,回溯时压缩路径 { if (x != parent[x]) { parent[x] = find(parent[x]); //回溯时的压缩路径 } //从x结点搜索到祖先结点所经过的结点都指向该祖先结点 return parent[x]; }
int findx(int x) { if(x == pa[x]) return x; int t = findx(pa[x]); pa[x] = t; return pa[x]; }
和上面那个一样。但最后一题这个比较好
for(int i = 1; i <= n; i++) { if(i == findx(i)) res++; }
//找有多少个连通分量。
题目地址:https://vjudge.net/contest/280900#problem/M
题意:共n个数,p个操作。输入p。有两个操作M和C。M x y表示把x所在的栈放到y所在的栈上(比如M 2 6:[2 4]放到[1 6]上为[2 4 1 6]),C x为输出x下面有几个数。
思路:并查集每个集合以栈最下面的数为根,维护两个数组num[x]表示x所在集合节点总数,count[x]表示x下方节点个数。每次查找压缩路径的时候更新count(换父节点的时候每轮都把父节点的count加给儿子,就可以一直更新到x所在栈的最底下),合并的时候更新px的count和py的num(把x的栈放到y的栈上,x下面多了num[y]个节点,新栈总根y总数增加num[x]个)。【不管是M还是C,先found一下x,把x的fa[x]更新到栈的最底下,count[x]才是真正的数!】
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<vector> using namespace std; const int maxm = 30005; int pa[maxm], rak[maxm], tot[maxm];//tot总的数量,rak下面的数量 void make_set(int x) { pa[x] = x; rak[x] = 0; tot[x] = 1; } int findx(int a) { if(a == pa[a]) return a; int t = findx(pa[a]); rak[a] += rak[pa[a]]; pa[a] = t; return pa[a]; }//因为是那爹的数量加上,所以这个压缩写法比较好,而不是加上祖先的。 void unio(int x, int y) { x = findx(x); y = findx(y); if(x == y)return ; // if(rak[x] > rak[y])/*让rank比较高的作为父结点*/ // { // pa[y] = x; // num[x] += num[y]; // } // else // { // pa[x] = y; // if(rak[x] == rak[y]) // rak[y]++; // num[y] += num[x]; // } pa[x] = y; rak[x] = tot[y]; tot[y] += tot[x]; // printf("%d ", rak[x]); } int n, x, y; char ch[3]; int main() { scanf("%d", &n); for(int i = 1; i <= 30000; i++) { make_set(i); } while(n--) { scanf("%s", ch); if(ch[0] == 'M') { scanf("%d%d", &x, &y); unio(x, y); } else if(ch[0] == 'C') { scanf("%d", &x); findx(x);//注意这里还要压缩一下 printf("%d ", rak[x]); } } return 0; }