并查集和模拟堆
Posted kukudewen
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并查集和模拟堆相关的知识,希望对你有一定的参考价值。
并查集:
1.将两个集合合并。
2.询问两个元素是否在一个集合当中。
基本原理:每个集合用一颗树来表示。树根的编号就是整个集合的编号。每个节点存储他的父亲节点,p[x]表示x的父亲节点。
问题1:如何判断树根:if(p[x]==x);
问题2:如何求x的集合的编号:while(p[x]!=x) x=p[x];
问题3:如何合并两个集合:px是x的集合编号,py是y的集合编号,p[x]=y;
并查集板子
(1)朴素并查集: int p[N]; //存储每个点的祖宗节点 // 返回x的祖宗节点 int find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x]; } // 初始化,假定节点编号是1~n for (int i = 1; i <= n; i ++ ) p[i] = i; // 合并a和b所在的两个集合: p[find(a)] = find(b); (2)维护size的并查集: int p[N], size[N]; //p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量 // 返回x的祖宗节点 int find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x]; } // 初始化,假定节点编号是1~n for (int i = 1; i <= n; i ++ ) { p[i] = i; size[i] = 1; } // 合并a和b所在的两个集合: size[find(b)] += size[find(a)]; p[find(a)] = find(b); (3)维护到祖宗节点距离的并查集: int p[N], d[N]; //p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离 // 返回x的祖宗节点 int find(int x) { if (p[x] != x) { int u = find(p[x]); d[x] += d[p[x]]; p[x] = u; } return p[x]; } // 初始化,假定节点编号是1~n for (int i = 1; i <= n; i ++ ) { p[i] = i; d[i] = 0; } // 合并a和b所在的两个集合: p[find(a)] = find(b); d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量
题目1:
一共有n个数,编号是1~n,最开始每个数各自在一个集合中。
现在要进行m个操作,操作共有两种:
- “M a b”,将编号为a和b的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
- “Q a b”,询问编号为a和b的两个数是否在同一个集合中;
输入格式
第一行输入整数n和m。
接下来m行,每行包含一个操作指令,指令为“M a b”或“Q a b”中的一种。
输出格式
对于每个询问指令”Q a b”,都要输出一个结果,如果a和b在同一集合内,则输出“Yes”,否则输出“No”。
每个结果占一行。
数据范围
1≤n,m≤1051≤n,m≤105
输入样例
4 5 M 1 2 M 3 4 Q 1 2 Q 1 3 Q 3 4
输出样例
Yes
No
Yes
代码实现
#include<bits/stdc++.h> using namespace std; const int N=100010; int p[N]; int find(int x) { if(p[x]!=x) p[x]=find(p[x]); return p[x]; } int main() { int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) p[i]=i; while(m--) { char op[2]; int a,b; scanf("%s%d%d",op,&a,&b); if(op[0]==‘M‘) p[find(a)]=find(b); else { if(find(a)==find(b)) cout<<"Yes"<<endl; else cout<<"No"<<endl; } } }
题目2:
给定一个包含n个点(编号为1~n)的无向图,初始时图中没有边。
现在要进行m个操作,操作共有三种:
- “C a b”,在点a和点b之间连一条边,a和b可能相等;
- “Q1 a b”,询问点a和点b是否在同一个连通块中,a和b可能相等;
- “Q2 a”,询问点a所在连通块中点的数量;
输入格式
第一行输入整数n和m。
接下来m行,每行包含一个操作指令,指令为“C a b”,“Q1 a b”或“Q2 a”中的一种。
输出格式
对于每个询问指令”Q1 a b”,如果a和b在同一个连通块中,则输出“Yes”,否则输出“No”。
对于每个询问指令“Q2 a”,输出一个整数表示点a所在连通块中点的数量
每个结果占一行。
数据范围
1≤n,m≤1051≤n,m≤105
输入样例
5 5 C 1 2 Q1 1 2 Q2 1 C 2 5 Q2 5
输出样例
Yes 2 3
模拟堆:
1.插入一个数 heap[++size]=x;up(size);
2.求集合当中的最小值 heap[1];
3.删除最小值 heap[1]=heap[size];size--;down(1);
4.删除任意一个元素 heap[k]=heap[size];size--;down(k);
5.修改任意一个元素 heap[k]=x;down(k);up(k);
堆模板
// h[N]存储堆中的值, h[1]是堆顶,x的左儿子是2x, 右儿子是2x + 1 // ph[k]存储第k个插入的点在堆中的位置 // hp[k]存储堆中下标是k的点是第几个插入的 int h[N], ph[N], hp[N], size; // 交换两个点,及其映射关系 void heap_swap(int a, int b) { swap(ph[hp[a]],ph[hp[b]]); swap(hp[a], hp[b]); swap(h[a], h[b]); } void down(int u) { int t = u; if (u * 2 <= size && h[u * 2] < h[t]) t = u * 2; if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1; if (u != t) { heap_swap(u, t); down(t); } } void up(int u) { while (u / 2 && h[u] < h[u / 2]) { heap_swap(u, u / 2); u >>= 1; } } // O(n)建堆 for (int i = n / 2; i; i -- ) down(i);
题目1:
维护一个集合,初始时集合为空,支持如下几种操作:
- “I x”,插入一个数x;
- “PM”,输出当前集合中的最小值;
- “DM”,删除当前集合中的最小值(数据保证此时的最小值唯一);
- “D k”,删除第k个插入的数;
- “C k x”,修改第k个插入的数,将其变为x;
现在要进行N次操作,对于所有第2个操作,输出当前集合的最小值。
输入格式
第一行包含整数N。
接下来N行,每行包含一个操作指令,操作指令为”I x”,”PM”,”DM”,”D k”或”C k x”中的一种。
输出格式
对于每个输出指令“PM”,输出一个结果,表示当前集合中的最小值。
每个结果占一行。
数据范围
1≤N≤1051≤N≤105
−109≤x≤109−109≤x≤109
数据保证合法。
输入样例
8 I -10 PM I -10 D 1 C 2 8 I 6 PM DM
输出样例:
-10 6
代码实现
#include<bits/stdc++.h> using namespace std; const int N=100010; int h[N],hp[N],ph[N],cnt; void heap_swap(int a,int b) { swap(ph[hp[a]],ph[hp[b]]); swap(hp[a],hp[b]); swap(h[a],h[b]); } void down(int u) { int t = u; if (u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2; if (u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1; if (u != t) { heap_swap(u, t); down(t); } } void up(int u) { while(u/2&&h[u]<h[u/2]) { heap_swap(u,u/2); u=u/2; } } int main() { int n, m = 0; scanf("%d", &n); while (n -- ) { char op[5]; int k, x; scanf("%s", op); if (!strcmp(op, "I")) { scanf("%d", &x); cnt ++ ; m ++ ; ph[m] = cnt, hp[cnt] = m; h[cnt] = x; up(cnt); } else if (!strcmp(op, "PM")) printf("%d ", h[1]); else if (!strcmp(op, "DM")) { heap_swap(1, cnt); cnt -- ; down(1); } else if (!strcmp(op, "D")) { scanf("%d", &k); k = ph[k]; heap_swap(k, cnt); cnt -- ; up(k); down(k); } else { scanf("%d%d", &k, &x); k = ph[k]; h[k] = x; up(k); down(k); } } return 0; }
以上是关于并查集和模拟堆的主要内容,如果未能解决你的问题,请参考以下文章
HDU - 1233 还是畅通工程(带权并查集和最小生成树)
BZOJ4388JOI2012 invitation 堆+线段树+并查集模拟Prim