算法模板:堆,最小生成树(Prim,Kruskal),快速幂
-
堆
-
堆一般以二叉堆的形式存在,也可以说是一个“优先队列”,堆中数据以一定顺序存放,比如小根堆(根节点小于子节点)
-
堆涉及到的基本操作:
- 上浮:新插入元素时在底,上浮到相应的位置
- 下沉:删除元素时,删除最小元素(根节点),以堆底元素作为临时的root,然后进行下沉调整堆
-
基本模板:
- 堆结构:一个数组,heap[0]来存储堆中元素个数,heap[heap[0]]是堆底元素,heap[heap[0]/2]是堆底元素的父节点
int heap[MAXN];
- 上浮:
void swim(int x) { int p = x >> 1; while(p>0 && heap[x]<heap[p]) { swap(heap[x],heap[p]);//子节点<父节点,则互换位置 x = p; p = x >> 1; //继续上浮 } }
- 下沉:
void sink(int x) { int c = x << 1; while(c <= heap[0]) { if(c+1<=heap[0] && heap[c+1]<heap[c]) c++; if(heap[c] < heap[x]) { swap(heap[c],heap[x]); x = c; c = x << 1; } else { break; } } }
- 插入:
void insert(int x) { heap[0] ++; heap[heap[0]] = x; swim(heap[0]); }
- 删除
void pop() { swap(heap[1],heap[heap[0]--]); sink(1); }
-
-
最小生成树
-
Prim && Kruskal :
-
Prim: 以连接节点的临边找最小值
-
Kruskal: 直接排序边
-
稠密图用Prim,稀疏图用Kruskal
-
-
Prim :
-
1、从任意一个顶点开始构造生成树,假设就从1号顶点吧, 首先将顶点1加入生成树中,用一个一维数组vis来标记 哪些顶点已经加入了生成树。
2、用数组dis记录生成树到各个顶点的距离,最初生成树中之后1号 顶点,有直连边时,数组dis中存储的就是1号顶点到该顶点 的边的权值,没有直连边的时候就是无穷大,即初始化dis数组。
3、从数组dis中选出离生成树最近的顶点(假设这个顶点为j) 加入到生成树中(即在数组dis中找到最小值)。再以j为中间点, 更新生成树到每一个非树顶点的距离(就是松弛啦), 即如果dis[k]>e[j][k]则更新dis[k]=e[j][k]。
4、重复第三步,直到生成树中有n个顶点为止。 -
以链式前向星存图
struct Edge { int val,prev,next; }e[MAXN];
-
添加一条边
void add(int p,int n,int v) { e[++k].prev = first[p]; first[p] = k; e[k].next = n; e[k].val = v; }
-
priority_queue<pair<int,int> > q; int prim() { q.push(make_pair(0,1)); while(!q.empty() && cnt<n) { int now = q.top().second; int v = q.top().first; q.pop(); if(vis[now]) continue; vis[now] = 1; ans += v; cnt ++; for(int i=first[now]; i; i=e[i].prev) { if(!vis[e[i].next]) q.push(make_pair(-e[i].val,e[i].next)); //优先队列默认大根堆 } } return -ans; }
-
-
Kruskal:
- 主要思路:kruskal就是基于并查集的贪心算法
- 输入边
- 结构体排序,以小到大排边
- 建并查集,联通树
- 存储边
struct edge { int val, prev, next; } e[MAXN]; bool cmp(const edge& a, const edge& b) { return a.val < b.val; }
- 并查集
int pre[MAXN]; int find(int x) { while (x != pre[x]) { x = pre[x] = pre[pre[x]]; } return x; }
- kruskal:
void kruskal() { for (int i = 1; i <= m; i++) { int fx = find(e[i].prev); int fy = find(e[i].next); if (fx != fy) { ans += e[i].val; cnt++; pre[fx] = fy; } else { continue; } if (cnt == n-1) break; } }
- 主要思路:kruskal就是基于并查集的贪心算法
-
-
快速幂 || 快速幂取余
-
每一步把指数折半,而相应的底数平方,从而减少循环次数
eg. 3^10 == (3x3)^5 == 9 x (9x9)^2 == 9 x 81 x 81 x 81
-
取余公式:
(a + b) % p = (a % p + b % p) % p (a - b) % p = (a % p - b % p) % p (a * b) % p = (a % p * b % p) % p
-
模板:
LL quickPower(LL base,LL power,LL modk) { LL ans = 1; while(power > 0) { if(power & 1) { //奇数次乘入结果 ans = ans * base % modk; } power >>= 1; //power折半 base = (base * base) % modk; //底数平方 } return ans%modk; }
-